commit b2dde724ca2b55885cbfdf14c79fb3431f7bb371 Author: = <=> Date: Sun Jul 21 14:20:26 2024 +0800 Initial diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json new file mode 100644 index 0000000..a9ebc72 --- /dev/null +++ b/.cargo_vcs_info.json @@ -0,0 +1,5 @@ +{ + "git": { + "sha1": "954c68457fda6f1b9b87393f3130f74da5f548f8" + } +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1b4b019 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# http://editorconfig.org + +root = true + +[*.rs] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 4 +max_line_length = 120 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5551cda --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target +tmp +Cargo.lock diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..68a8a15 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,35 @@ +language: rust +env: + - RUST_LOG=warn RUST_BACKTRACE=1 +matrix: + include: + # Minimal supported rustc version + - rust: 1.24.0 + script: + # Build only the library (examples may fail) + - cargo update + - cargo update -p byteorder --precise 1.3.4 + - cargo update -p cfg-if --precise 0.1.9 + - cargo update -p log --precise 0.4.8 + - cargo build --verbose + - rust: stable + - rust: beta + - rust: nightly + - rust: nightly-2018-03-07 + script: + # nostd build + # byteorder crate version is configured here to fix build in old nightly compiler + # See: https://github.com/BurntSushi/byteorder/pull/150 + - cargo update + - cargo update -p byteorder --precise 1.2.7 + - cargo update -p cfg-if --precise 0.1.9 + - cargo update -p log --precise 0.4.8 + - cargo build --verbose --no-default-features --features core_io + - cargo build --verbose --no-default-features --features core_io,alloc + - rust: nightly-2019-07-01 + script: + - cargo build --verbose --no-default-features --features core_io + - cargo build --verbose --no-default-features --features core_io,alloc + + allow_failures: + - rust: nightly diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..fb991d2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,61 @@ +Changelog +========= + +0.3.5 (2021-01-23) +------------------------ +Bug fixes: +* Fix file-system corruption that occurs when creating multiple directory entries in a non-root directory (directory + size must be greater or equal to the cluster size for the corruption to happen) + +0.3.4 (2020-07-20) +------------------ +Bug fixes: +* Fix time encoding and decoding in directory entries + +0.3.3 (2019-11-10) +------------------ +Bug fixes: +* Add missing characters to the whitelist for long file name (`^`, `#`, `&`) +* Fix invalid short file names for `.` and `..` entries when creating a new directory +* Fix `no_std` build + +Misc changes: +* Fix compiler warnings +* Improve documentation + +0.3.2 (2018-12-29) +------------------ +New features: +* Add `format_volume` function for initializing a FAT filesystem on a partition +* Add more checks of filesystem correctness when mounting + +Bug fixes: +* Clear directory returned from `create_dir` method - upgrade ASAP if this method is used +* Fix handling of FSInfo sector on FAT32 volumes with sector size different than 512 - upgrade ASAP if such sector size is used +* Use `write_all` in `serialize` method for FSInfo sector - previously it could have been improperly updated + +0.3.1 (2018-10-20) +------------------ +New features: +* Increased file creation time resolution from 2s to 1/100s +* Added oem_cp_converter filesystem option allowing to provide custom short name decoder +* Added time_provider filesystem option allowing to provide time used when modifying directory entries +* Added marking volume as dirty on first write and not-dirty on unmount +* Added support for reading volume label from root directory + +Bug fixes: +* Fixed handling of short names with spaces in the middle - all characters after first space in 8.3 components were + stripped before +* Fixed decoding 0xE5 character in first byte of short name - if first character of short name is equal to 0xE5, + it was read as 0x05 +* Preserve 4 most significant bits in FAT32 entries - it is required by FAT specification, but previous behavior + should not cause any compatibility problem because all known implementations ignore those bits +* Fixed warnings for file entries without LFN entries - they were handled properly, but caused warnings in run-time + +Misc changes: +* Deprecated set_created. set_accessed, set_modified methods on File - those fields are updated automatically using + information provided by TimeProvider +* Fixed size formatting in ls.rs example +* Added more filesystem checks causing errors or warnings when incompatibility is detected +* Removed unnecessary clone() calls +* Code formatting and docs fixes diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..af639d9 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,52 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies +# +# If you believe there's an error in this file please file an +# issue against the rust-lang/cargo repository. If you're +# editing this file be aware that the upstream Cargo.toml +# will likely look very different (and much more reasonable) + +[package] +name = "fatfs" +version = "0.3.5" +authors = ["Rafał Harabień "] +exclude = ["resources/*"] +description = "FAT filesystem library.\n" +readme = "README.md" +keywords = ["fat", "filesystem", "no_std"] +categories = ["filesystem"] +license = "MIT" +repository = "https://github.com/rafalh/rust-fatfs" +[dependencies.bitflags] +version = "1.0" + +[dependencies.byteorder] +version = "1" +default-features = false + +[dependencies.chrono] +version = "0.4" +optional = true + +[dependencies.core_io] +version = "0.1" +optional = true + +[dependencies.log] +version = "0.4" +[dev-dependencies.env_logger] +version = "0.5" + +[dev-dependencies.fscommon] +version = "0.1" + +[features] +alloc = [] +default = ["chrono", "std", "alloc"] +std = ["byteorder/std"] +[badges.travis-ci] +repository = "rafalh/rust-fatfs" diff --git a/Cargo.toml.orig b/Cargo.toml.orig new file mode 100644 index 0000000..362a9a1 --- /dev/null +++ b/Cargo.toml.orig @@ -0,0 +1,37 @@ +[package] +name = "fatfs" +version = "0.3.5" +authors = ["Rafał Harabień "] +repository = "https://github.com/rafalh/rust-fatfs" +readme = "README.md" +keywords = ["fat", "filesystem", "no_std"] +categories = ["filesystem"] +license = "MIT" +description = """ +FAT filesystem library. +""" +exclude = [ + "resources/*", +] + +[badges] +travis-ci = { repository = "rafalh/rust-fatfs" } + +[features] +# Use Rust std library +std = ["byteorder/std"] +# Use dynamic allocation - required for LFN support. When used without std please enable core_io/collections +alloc = [] +# Default features +default = ["chrono", "std", "alloc"] + +[dependencies] +byteorder = { version = "1", default-features = false } +bitflags = "1.0" +log = "0.4" +chrono = { version = "0.4", optional = true } +core_io = { version = "0.1", optional = true } + +[dev-dependencies] +env_logger = "0.5" +fscommon = "0.1" diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..5054c87 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright 2017 Rafał Harabień + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..eaacb15 --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +Rust FAT FS +=========== + +[![Travis Build Status](https://travis-ci.org/rafalh/rust-fatfs.svg?branch=master)](https://travis-ci.org/rafalh/rust-fatfs) +[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.txt) +[![crates.io](http://meritbadge.herokuapp.com/fatfs)](https://crates.io/crates/fatfs) +[![Documentation](https://docs.rs/fatfs/badge.svg)](https://docs.rs/fatfs) +[![Minimum rustc version](https://img.shields.io/badge/rustc-1.24+-yellow.svg)](https://blog.rust-lang.org/2018/02/15/Rust-1.24.html) + +A FAT filesystem library implemented in Rust. + +Features: +* read/write file using standard Read/Write traits +* read directory contents +* create/remove file or directory +* rename/move file or directory +* read/write file timestamps (updated automatically if `chrono` feature is enabled) +* format volume +* FAT12, FAT16, FAT32 compatibility +* LFN (Long File Names) extension is supported +* Basic no_std environment support + +Usage +----- + +Add this to your `Cargo.toml`: + + [dependencies] + fatfs = "0.3" + +and this to your crate root: + + extern crate fatfs; + +You can start using the `fatfs` library now: + + let img_file = File::open("fat.img")?; + let fs = fatfs::FileSystem::new(img_file, fatfs::FsOptions::new())?; + let root_dir = fs.root_dir(); + let mut file = root_dir.create_file("hello.txt")?; + file.write_all(b"Hello World!")?; + +Note: it is recommended to wrap the underlying file struct in a buffering/caching object like `BufStream` from `fscommon` crate. For example: + + extern crate fscommon; + let buf_stream = BufStream::new(img_file); + let fs = fatfs::FileSystem::new(buf_stream, fatfs::FsOptions::new())?; + +See more examples in the `examples` subdirectory. + +no_std usage +------------ + +Add this to your `Cargo.toml`: + + [dependencies] + fatfs = { version = "0.3", features = ["core_io"], default-features = false } + +For building in `no_std` mode a Rust compiler version compatible with `core_io` crate is required. + +For now `core_io` supports only nightly Rust channel. See a date suffix in latest `core_io` crate version for exact +compiler version. + +Additional features: + +* `alloc` - use `alloc` crate for dynamic allocation. Required for LFN (long file name) support and API which uses +`String` type. You may have to provide a memory allocator implementation. + +Note: above feature is enabled by default. + +License +------- +The MIT license. See `LICENSE.txt`. diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..389421a --- /dev/null +++ b/TODO.md @@ -0,0 +1,4 @@ +TODO +==== +* add method for getting `DirEntry` from a path (possible names: metadata, lookup) +* do not create LFN entries if the name fits in a SFN entry diff --git a/build-nostd.sh b/build-nostd.sh new file mode 100755 index 0000000..3982407 --- /dev/null +++ b/build-nostd.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e +cargo build --no-default-features --features core_io +cargo build --no-default-features --features core_io,alloc,core_io/collections diff --git a/examples/cat.rs b/examples/cat.rs new file mode 100644 index 0000000..375996f --- /dev/null +++ b/examples/cat.rs @@ -0,0 +1,21 @@ +extern crate fatfs; +extern crate fscommon; + +use std::env; +use std::fs::File; +use std::io::{self, prelude::*}; + +use fatfs::{FileSystem, FsOptions}; +use fscommon::BufStream; + +fn main() -> io::Result<()> { + let file = File::open("resources/fat32.img")?; + let buf_rdr = BufStream::new(file); + let fs = FileSystem::new(buf_rdr, FsOptions::new())?; + let root_dir = fs.root_dir(); + let mut file = root_dir.open_file(&env::args().nth(1).expect("filename expected"))?; + let mut buf = vec![]; + file.read_to_end(&mut buf)?; + print!("{}", String::from_utf8_lossy(&buf)); + Ok(()) +} diff --git a/examples/ls.rs b/examples/ls.rs new file mode 100644 index 0000000..84bd466 --- /dev/null +++ b/examples/ls.rs @@ -0,0 +1,44 @@ +extern crate chrono; +extern crate fatfs; +extern crate fscommon; + +use std::env; +use std::fs::File; +use std::io; + +use chrono::{DateTime, Local}; +use fatfs::{FileSystem, FsOptions}; +use fscommon::BufStream; + +fn format_file_size(size: u64) -> String { + const KB: u64 = 1024; + const MB: u64 = 1024 * KB; + const GB: u64 = 1024 * MB; + if size < KB { + format!("{}B", size) + } else if size < MB { + format!("{}KB", size / KB) + } else if size < GB { + format!("{}MB", size / MB) + } else { + format!("{}GB", size / GB) + } +} + +fn main() -> io::Result<()> { + let file = File::open("resources/fat32.img")?; + let buf_rdr = BufStream::new(file); + let fs = FileSystem::new(buf_rdr, FsOptions::new())?; + let root_dir = fs.root_dir(); + let dir = match env::args().nth(1) { + None => root_dir, + Some(ref path) if path == "." => root_dir, + Some(ref path) => root_dir.open_dir(&path)?, + }; + for r in dir.iter() { + let e = r?; + let modified = DateTime::::from(e.modified()).format("%Y-%m-%d %H:%M:%S").to_string(); + println!("{:4} {} {}", format_file_size(e.len()), modified, e.file_name()); + } + Ok(()) +} diff --git a/examples/mkfatfs.rs b/examples/mkfatfs.rs new file mode 100644 index 0000000..cd0d40a --- /dev/null +++ b/examples/mkfatfs.rs @@ -0,0 +1,16 @@ +extern crate fatfs; +extern crate fscommon; + +use std::env; +use std::fs; +use std::io; + +use fscommon::BufStream; + +fn main() -> io::Result<()> { + let filename = env::args().nth(1).expect("image path expected"); + let file = fs::OpenOptions::new().read(true).write(true).open(&filename)?; + let buf_file = BufStream::new(file); + fatfs::format_volume(buf_file, fatfs::FormatVolumeOptions::new())?; + Ok(()) +} diff --git a/examples/partition.rs b/examples/partition.rs new file mode 100644 index 0000000..417dfb4 --- /dev/null +++ b/examples/partition.rs @@ -0,0 +1,25 @@ +extern crate fatfs; +extern crate fscommon; + +use std::{fs, io}; + +use fatfs::{FileSystem, FsOptions}; +use fscommon::{BufStream, StreamSlice}; + +fn main() -> io::Result<()> { + // Open disk image + let file = fs::File::open("resources/fat32.img")?; + // Provide sample partition localization. In real application it should be read from MBR/GPT. + let first_lba = 0; + let last_lba = 10000; + // Create partition using provided start address and size in bytes + let partition = StreamSlice::new(file, first_lba, last_lba + 1)?; + // Create buffered stream to optimize file access + let buf_rdr = BufStream::new(partition); + // Finally initialize filesystem struct using provided partition + let fs = FileSystem::new(buf_rdr, FsOptions::new())?; + // Read and display volume label + println!("Volume Label: {}", fs.volume_label()); + // other operations... + Ok(()) +} diff --git a/examples/write.rs b/examples/write.rs new file mode 100644 index 0000000..600d754 --- /dev/null +++ b/examples/write.rs @@ -0,0 +1,24 @@ +extern crate fatfs; +extern crate fscommon; + +use std::fs::OpenOptions; +use std::io::{self, prelude::*}; + +use fatfs::{FileSystem, FsOptions}; +use fscommon::BufStream; + +fn main() -> io::Result<()> { + let img_file = match OpenOptions::new().read(true).write(true).open("fat.img") { + Ok(file) => file, + Err(err) => { + println!("Failed to open image!"); + return Err(err); + }, + }; + let buf_stream = BufStream::new(img_file); + let options = FsOptions::new().update_accessed_date(true); + let fs = FileSystem::new(buf_stream, options)?; + let mut file = fs.root_dir().create_file("hello.txt")?; + file.write_all(b"Hello World!")?; + Ok(()) +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..b9a430e --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,8 @@ +max_width = 120 +match_block_trailing_comma = true +use_field_init_shorthand = true +ignore = [ + "src/byteorder_core_io.rs", +] +unstable_features = true +use_small_heuristics = "Max" diff --git a/scripts/create-test-img.sh b/scripts/create-test-img.sh new file mode 100755 index 0000000..deaaa8c --- /dev/null +++ b/scripts/create-test-img.sh @@ -0,0 +1,27 @@ +#!/bin/sh +OUT_DIR=../resources +set -e + +create_test_img() { + local name=$1 + local blkcount=$2 + local fatSize=$3 + dd if=/dev/zero of="$name" bs=1024 count=$blkcount + mkfs.vfat -s 1 -F $fatSize -n "Test!" -i 12345678 "$name" + mkdir -p mnt + sudo mount -o loop "$name" mnt -o rw,uid=$USER,gid=$USER + for i in $(seq 1 1000); do + echo "Rust is cool!" >>"mnt/long.txt" + done + echo "Rust is cool!" >>"mnt/short.txt" + mkdir -p "mnt/very/long/path" + echo "Rust is cool!" >>"mnt/very/long/path/test.txt" + mkdir -p "mnt/very-long-dir-name" + echo "Rust is cool!" >>"mnt/very-long-dir-name/very-long-file-name.txt" + + sudo umount mnt +} + +create_test_img "$OUT_DIR/fat12.img" 1000 12 +create_test_img "$OUT_DIR/fat16.img" 2500 16 +create_test_img "$OUT_DIR/fat32.img" 34000 32 diff --git a/src/boot_sector.rs b/src/boot_sector.rs new file mode 100644 index 0000000..4faf13a --- /dev/null +++ b/src/boot_sector.rs @@ -0,0 +1,884 @@ +use core::cmp; +use core::u16; +use core::u8; +use io; +use io::prelude::*; +use io::{Error, ErrorKind}; + +use byteorder::LittleEndian; +use byteorder_ext::{ReadBytesExt, WriteBytesExt}; + +use dir_entry::DIR_ENTRY_SIZE; +use fs::{FatType, FormatVolumeOptions, FsStatusFlags}; +use table::RESERVED_FAT_ENTRIES; + +const BITS_PER_BYTE: u32 = 8; +const KB: u64 = 1024; +const MB: u64 = KB * 1024; +const GB: u64 = MB * 1024; + +#[derive(Default, Debug, Clone)] +pub(crate) struct BiosParameterBlock { + pub(crate) bytes_per_sector: u16, + pub(crate) sectors_per_cluster: u8, + pub(crate) reserved_sectors: u16, + pub(crate) fats: u8, + pub(crate) root_entries: u16, + pub(crate) total_sectors_16: u16, + pub(crate) media: u8, + pub(crate) sectors_per_fat_16: u16, + pub(crate) sectors_per_track: u16, + pub(crate) heads: u16, + pub(crate) hidden_sectors: u32, + pub(crate) total_sectors_32: u32, + + // Extended BIOS Parameter Block + pub(crate) sectors_per_fat_32: u32, + pub(crate) extended_flags: u16, + pub(crate) fs_version: u16, + pub(crate) root_dir_first_cluster: u32, + pub(crate) fs_info_sector: u16, + pub(crate) backup_boot_sector: u16, + pub(crate) reserved_0: [u8; 12], + pub(crate) drive_num: u8, + pub(crate) reserved_1: u8, + pub(crate) ext_sig: u8, + pub(crate) volume_id: u32, + pub(crate) volume_label: [u8; 11], + pub(crate) fs_type_label: [u8; 8], +} + +impl BiosParameterBlock { + fn deserialize(rdr: &mut T) -> io::Result { + let mut bpb: BiosParameterBlock = Default::default(); + bpb.bytes_per_sector = rdr.read_u16::()?; + bpb.sectors_per_cluster = rdr.read_u8()?; + bpb.reserved_sectors = rdr.read_u16::()?; + bpb.fats = rdr.read_u8()?; + bpb.root_entries = rdr.read_u16::()?; + bpb.total_sectors_16 = rdr.read_u16::()?; + bpb.media = rdr.read_u8()?; + bpb.sectors_per_fat_16 = rdr.read_u16::()?; + bpb.sectors_per_track = rdr.read_u16::()?; + bpb.heads = rdr.read_u16::()?; + bpb.hidden_sectors = rdr.read_u32::()?; + bpb.total_sectors_32 = rdr.read_u32::()?; + + if bpb.is_fat32() { + bpb.sectors_per_fat_32 = rdr.read_u32::()?; + bpb.extended_flags = rdr.read_u16::()?; + bpb.fs_version = rdr.read_u16::()?; + bpb.root_dir_first_cluster = rdr.read_u32::()?; + bpb.fs_info_sector = rdr.read_u16::()?; + bpb.backup_boot_sector = rdr.read_u16::()?; + rdr.read_exact(&mut bpb.reserved_0)?; + bpb.drive_num = rdr.read_u8()?; + bpb.reserved_1 = rdr.read_u8()?; + bpb.ext_sig = rdr.read_u8()?; // 0x29 + bpb.volume_id = rdr.read_u32::()?; + rdr.read_exact(&mut bpb.volume_label)?; + rdr.read_exact(&mut bpb.fs_type_label)?; + } else { + bpb.drive_num = rdr.read_u8()?; + bpb.reserved_1 = rdr.read_u8()?; + bpb.ext_sig = rdr.read_u8()?; // 0x29 + bpb.volume_id = rdr.read_u32::()?; + rdr.read_exact(&mut bpb.volume_label)?; + rdr.read_exact(&mut bpb.fs_type_label)?; + } + + // when the extended boot signature is anything other than 0x29, the fields are invalid + if bpb.ext_sig != 0x29 { + // fields after ext_sig are not used - clean them + bpb.volume_id = 0; + bpb.volume_label = [0; 11]; + bpb.fs_type_label = [0; 8]; + } + + Ok(bpb) + } + + fn serialize(&self, mut wrt: T) -> io::Result<()> { + wrt.write_u16::(self.bytes_per_sector)?; + wrt.write_u8(self.sectors_per_cluster)?; + wrt.write_u16::(self.reserved_sectors)?; + wrt.write_u8(self.fats)?; + wrt.write_u16::(self.root_entries)?; + wrt.write_u16::(self.total_sectors_16)?; + wrt.write_u8(self.media)?; + wrt.write_u16::(self.sectors_per_fat_16)?; + wrt.write_u16::(self.sectors_per_track)?; + wrt.write_u16::(self.heads)?; + wrt.write_u32::(self.hidden_sectors)?; + wrt.write_u32::(self.total_sectors_32)?; + + if self.is_fat32() { + wrt.write_u32::(self.sectors_per_fat_32)?; + wrt.write_u16::(self.extended_flags)?; + wrt.write_u16::(self.fs_version)?; + wrt.write_u32::(self.root_dir_first_cluster)?; + wrt.write_u16::(self.fs_info_sector)?; + wrt.write_u16::(self.backup_boot_sector)?; + wrt.write_all(&self.reserved_0)?; + wrt.write_u8(self.drive_num)?; + wrt.write_u8(self.reserved_1)?; + wrt.write_u8(self.ext_sig)?; // 0x29 + wrt.write_u32::(self.volume_id)?; + wrt.write_all(&self.volume_label)?; + wrt.write_all(&self.fs_type_label)?; + } else { + wrt.write_u8(self.drive_num)?; + wrt.write_u8(self.reserved_1)?; + wrt.write_u8(self.ext_sig)?; // 0x29 + wrt.write_u32::(self.volume_id)?; + wrt.write_all(&self.volume_label)?; + wrt.write_all(&self.fs_type_label)?; + } + Ok(()) + } + + fn validate(&self) -> io::Result<()> { + // sanity checks + if self.bytes_per_sector.count_ones() != 1 { + return Err(Error::new(ErrorKind::Other, "invalid bytes_per_sector value in BPB (not power of two)")); + } else if self.bytes_per_sector < 512 { + return Err(Error::new(ErrorKind::Other, "invalid bytes_per_sector value in BPB (value < 512)")); + } else if self.bytes_per_sector > 4096 { + return Err(Error::new(ErrorKind::Other, "invalid bytes_per_sector value in BPB (value > 4096)")); + } + + if self.sectors_per_cluster.count_ones() != 1 { + return Err(Error::new(ErrorKind::Other, "invalid sectors_per_cluster value in BPB (not power of two)")); + } else if self.sectors_per_cluster < 1 { + return Err(Error::new(ErrorKind::Other, "invalid sectors_per_cluster value in BPB (value < 1)")); + } else if self.sectors_per_cluster > 128 { + return Err(Error::new(ErrorKind::Other, "invalid sectors_per_cluster value in BPB (value > 128)")); + } + + // bytes per sector is u16, sectors per cluster is u8, so guaranteed no overflow in multiplication + let bytes_per_cluster = self.bytes_per_sector as u32 * self.sectors_per_cluster as u32; + let maximum_compatibility_bytes_per_cluster: u32 = 32 * 1024; + + if bytes_per_cluster > maximum_compatibility_bytes_per_cluster { + // 32k is the largest value to maintain greatest compatibility + // Many implementations appear to support 64k per cluster, and some may support 128k or larger + // However, >32k is not as thoroughly tested... + warn!("fs compatibility: bytes_per_cluster value '{}' in BPB exceeds '{}', and thus may be incompatible with some implementations", + bytes_per_cluster, maximum_compatibility_bytes_per_cluster); + } + + let is_fat32 = self.is_fat32(); + if self.reserved_sectors < 1 { + return Err(Error::new(ErrorKind::Other, "invalid reserved_sectors value in BPB")); + } else if !is_fat32 && self.reserved_sectors != 1 { + // Microsoft document indicates fat12 and fat16 code exists that presume this value is 1 + warn!( + "fs compatibility: reserved_sectors value '{}' in BPB is not '1', and thus is incompatible with some implementations", + self.reserved_sectors + ); + } + + if self.fats == 0 { + return Err(Error::new(ErrorKind::Other, "invalid fats value in BPB")); + } else if self.fats > 2 { + // Microsoft document indicates that few implementations support any values other than 1 or 2 + warn!( + "fs compatibility: numbers of FATs '{}' in BPB is greater than '2', and thus is incompatible with some implementations", + self.fats + ); + } + + if is_fat32 && self.root_entries != 0 { + return Err(Error::new(ErrorKind::Other, "Invalid root_entries value in BPB (should be zero for FAT32)")); + } + + if !is_fat32 && self.root_entries == 0 { + return Err(Error::new(ErrorKind::Other, "Empty root directory region defined in FAT12/FAT16 BPB")); + } + + if (u32::from(self.root_entries) * DIR_ENTRY_SIZE as u32) % u32::from(self.bytes_per_sector) != 0 { + warn!("Root entries should fill sectors fully"); + } + + if is_fat32 && self.total_sectors_16 != 0 { + return Err(Error::new( + ErrorKind::Other, + "Invalid total_sectors_16 value in BPB (should be zero for FAT32)", + )); + } + + if (self.total_sectors_16 == 0) == (self.total_sectors_32 == 0) { + return Err(Error::new( + ErrorKind::Other, + "Invalid BPB (total_sectors_16 or total_sectors_32 should be non-zero)", + )); + } + + if is_fat32 && self.sectors_per_fat_32 == 0 { + return Err(Error::new( + ErrorKind::Other, + "Invalid sectors_per_fat_32 value in BPB (should be non-zero for FAT32)", + )); + } + + if self.fs_version != 0 { + return Err(Error::new(ErrorKind::Other, "Unknown FS version")); + } + + if self.total_sectors() <= self.first_data_sector() { + return Err(Error::new(ErrorKind::Other, "Invalid BPB (total_sectors field value is too small)")); + } + + if is_fat32 && self.backup_boot_sector() >= self.reserved_sectors() { + return Err(Error::new(ErrorKind::Other, "Invalid BPB (backup boot-sector not in a reserved region)")); + } + + if is_fat32 && self.fs_info_sector() >= self.reserved_sectors() { + return Err(Error::new(ErrorKind::Other, "Invalid BPB (FSInfo sector not in a reserved region)")); + } + + let total_clusters = self.total_clusters(); + let fat_type = FatType::from_clusters(total_clusters); + if is_fat32 != (fat_type == FatType::Fat32) { + return Err(Error::new( + ErrorKind::Other, + "Invalid BPB (result of FAT32 determination from total number of clusters and sectors_per_fat_16 field differs)", + )); + } + if fat_type == FatType::Fat32 && total_clusters > 0x0FFF_FFFF { + return Err(Error::new(ErrorKind::Other, "Invalid BPB (too many clusters)")); + } + + let bits_per_fat_entry = fat_type.bits_per_fat_entry(); + let total_fat_entries = self.sectors_per_fat() * self.bytes_per_sector as u32 * 8 / bits_per_fat_entry as u32; + if total_fat_entries - RESERVED_FAT_ENTRIES < total_clusters { + warn!("FAT is too small compared to total number of clusters"); + } + + Ok(()) + } + + pub(crate) fn mirroring_enabled(&self) -> bool { + self.extended_flags & 0x80 == 0 + } + + pub(crate) fn active_fat(&self) -> u16 { + // The zero-based number of the active FAT is only valid if mirroring is disabled. + if self.mirroring_enabled() { + 0 + } else { + self.extended_flags & 0x0F + } + } + + pub(crate) fn status_flags(&self) -> FsStatusFlags { + FsStatusFlags::decode(self.reserved_1) + } + + pub(crate) fn is_fat32(&self) -> bool { + // because this field must be zero on FAT32, and + // because it must be non-zero on FAT12/FAT16, + // this provides a simple way to detect FAT32 + self.sectors_per_fat_16 == 0 + } + + pub(crate) fn sectors_per_fat(&self) -> u32 { + if self.is_fat32() { + self.sectors_per_fat_32 + } else { + self.sectors_per_fat_16 as u32 + } + } + + pub(crate) fn total_sectors(&self) -> u32 { + if self.total_sectors_16 == 0 { + self.total_sectors_32 + } else { + self.total_sectors_16 as u32 + } + } + + pub(crate) fn reserved_sectors(&self) -> u32 { + self.reserved_sectors as u32 + } + + pub(crate) fn root_dir_sectors(&self) -> u32 { + let root_dir_bytes = self.root_entries as u32 * DIR_ENTRY_SIZE as u32; + (root_dir_bytes + self.bytes_per_sector as u32 - 1) / self.bytes_per_sector as u32 + } + + pub(crate) fn sectors_per_all_fats(&self) -> u32 { + self.fats as u32 * self.sectors_per_fat() + } + + pub(crate) fn first_data_sector(&self) -> u32 { + let root_dir_sectors = self.root_dir_sectors(); + let fat_sectors = self.sectors_per_all_fats(); + self.reserved_sectors() + fat_sectors + root_dir_sectors + } + + pub(crate) fn total_clusters(&self) -> u32 { + let total_sectors = self.total_sectors(); + let first_data_sector = self.first_data_sector(); + let data_sectors = total_sectors - first_data_sector; + data_sectors / self.sectors_per_cluster as u32 + } + + pub(crate) fn bytes_from_sectors(&self, sectors: u32) -> u64 { + // Note: total number of sectors is a 32 bit number so offsets have to be 64 bit + (sectors as u64) * self.bytes_per_sector as u64 + } + + pub(crate) fn sectors_from_clusters(&self, clusters: u32) -> u32 { + // Note: total number of sectors is a 32 bit number so it should not overflow + clusters * (self.sectors_per_cluster as u32) + } + + pub(crate) fn cluster_size(&self) -> u32 { + self.sectors_per_cluster as u32 * self.bytes_per_sector as u32 + } + + pub(crate) fn clusters_from_bytes(&self, bytes: u64) -> u32 { + let cluster_size = self.cluster_size() as i64; + ((bytes as i64 + cluster_size - 1) / cluster_size) as u32 + } + + pub(crate) fn fs_info_sector(&self) -> u32 { + self.fs_info_sector as u32 + } + + pub(crate) fn backup_boot_sector(&self) -> u32 { + self.backup_boot_sector as u32 + } +} + +pub(crate) struct BootSector { + bootjmp: [u8; 3], + oem_name: [u8; 8], + pub(crate) bpb: BiosParameterBlock, + boot_code: [u8; 448], + boot_sig: [u8; 2], +} + +impl BootSector { + pub(crate) fn deserialize(rdr: &mut T) -> io::Result { + let mut boot: BootSector = Default::default(); + rdr.read_exact(&mut boot.bootjmp)?; + rdr.read_exact(&mut boot.oem_name)?; + boot.bpb = BiosParameterBlock::deserialize(rdr)?; + + if boot.bpb.is_fat32() { + rdr.read_exact(&mut boot.boot_code[0..420])?; + } else { + rdr.read_exact(&mut boot.boot_code[0..448])?; + } + rdr.read_exact(&mut boot.boot_sig)?; + Ok(boot) + } + + pub(crate) fn serialize(&self, mut wrt: T) -> io::Result<()> { + wrt.write_all(&self.bootjmp)?; + wrt.write_all(&self.oem_name)?; + self.bpb.serialize(&mut wrt)?; + + if self.bpb.is_fat32() { + wrt.write_all(&self.boot_code[0..420])?; + } else { + wrt.write_all(&self.boot_code[0..448])?; + } + wrt.write_all(&self.boot_sig)?; + Ok(()) + } + + pub(crate) fn validate(&self) -> io::Result<()> { + if self.boot_sig != [0x55, 0xAA] { + return Err(Error::new(ErrorKind::Other, "Invalid boot sector signature")); + } + if self.bootjmp[0] != 0xEB && self.bootjmp[0] != 0xE9 { + warn!("Unknown opcode {:x} in bootjmp boot sector field", self.bootjmp[0]); + } + self.bpb.validate()?; + Ok(()) + } +} + +impl Default for BootSector { + fn default() -> BootSector { + BootSector { + bootjmp: Default::default(), + oem_name: Default::default(), + bpb: Default::default(), + boot_code: [0; 448], + boot_sig: Default::default(), + } + } +} + +pub(crate) fn estimate_fat_type(total_bytes: u64) -> FatType { + // Used only to select cluster size if FAT type has not been overriden in options + if total_bytes < 4 * MB { + FatType::Fat12 + } else if total_bytes < 512 * MB { + FatType::Fat16 + } else { + FatType::Fat32 + } +} + +fn determine_bytes_per_cluster(total_bytes: u64, bytes_per_sector: u16, fat_type: Option) -> u32 { + let fat_type = fat_type.unwrap_or_else(|| estimate_fat_type(total_bytes)); + let bytes_per_cluster = match fat_type { + FatType::Fat12 => (total_bytes.next_power_of_two() / MB * 512) as u32, + FatType::Fat16 => { + if total_bytes <= 16 * MB { + 1 * KB as u32 + } else if total_bytes <= 128 * MB { + 2 * KB as u32 + } else { + (total_bytes.next_power_of_two() / (64 * MB) * KB) as u32 + } + }, + FatType::Fat32 => { + if total_bytes <= 260 * MB { + 512 + } else if total_bytes <= 8 * GB { + 4 * KB as u32 + } else { + (total_bytes.next_power_of_two() / (2 * GB) * KB) as u32 + } + }, + }; + const MAX_CLUSTER_SIZE: u32 = 32 * KB as u32; + debug_assert!(bytes_per_cluster.is_power_of_two()); + cmp::min(cmp::max(bytes_per_cluster, bytes_per_sector as u32), MAX_CLUSTER_SIZE) +} + +fn determine_sectors_per_fat( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + fat_type: FatType, + reserved_sectors: u16, + root_dir_sectors: u32, + fats: u8, +) -> u32 { + // + // FAT size formula transformations: + // + // Initial basic formula: + // size of FAT in bits >= (total number of clusters + 2) * bits per FAT entry + // + // Note: when computing number of clusters from number of sectors rounding down is used because partial clusters + // are not allowed + // Note: in those transformations '/' is a floating-point division (not a rounding towards zero division) + // + // data_sectors = total_sectors - reserved_sectors - fats * sectors_per_fat - root_dir_sectors + // total_clusters = floor(data_sectors / sectors_per_cluster) + // bits_per_sector = bytes_per_sector * 8 + // sectors_per_fat * bits_per_sector >= (total_clusters + 2) * bits_per_fat_entry + // sectors_per_fat * bits_per_sector >= (floor(data_sectors / sectors_per_cluster) + 2) * bits_per_fat_entry + // + // Note: omitting the floor function can cause the FAT to be bigger by 1 entry - negligible + // + // sectors_per_fat * bits_per_sector >= (data_sectors / sectors_per_cluster + 2) * bits_per_fat_entry + // t0 = total_sectors - reserved_sectors - root_dir_sectors + // sectors_per_fat * bits_per_sector >= ((t0 - fats * sectors_per_fat) / sectors_per_cluster + 2) * bits_per_fat_entry + // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= (t0 - fats * sectors_per_fat) / sectors_per_cluster + 2 + // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= t0 / sectors_per_cluster + 2 - fats * sectors_per_fat / sectors_per_cluster + // sectors_per_fat * bits_per_sector / bits_per_fat_entry + fats * sectors_per_fat / sectors_per_cluster >= t0 / sectors_per_cluster + 2 + // sectors_per_fat * (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) >= t0 / sectors_per_cluster + 2 + // sectors_per_fat >= (t0 / sectors_per_cluster + 2) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) + // + // Note: MS specification omits the constant 2 in calculations. This library is taking a better approach... + // + // sectors_per_fat >= ((t0 + 2 * sectors_per_cluster) / sectors_per_cluster) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) + // sectors_per_fat >= (t0 + 2 * sectors_per_cluster) / (sectors_per_cluster * bits_per_sector / bits_per_fat_entry + fats) + // + // Note: compared to MS formula this one can suffer from an overflow problem if u32 type is used + // + // When converting formula to integer types round towards a bigger FAT: + // * first division towards infinity + // * second division towards zero (it is in a denominator of the first division) + + let t0: u32 = total_sectors - u32::from(reserved_sectors) - root_dir_sectors; + let t1: u64 = u64::from(t0) + u64::from(2 * u32::from(sectors_per_cluster)); + let bits_per_cluster = u32::from(sectors_per_cluster) * u32::from(bytes_per_sector) * BITS_PER_BYTE; + let t2 = u64::from(bits_per_cluster / u32::from(fat_type.bits_per_fat_entry()) + u32::from(fats)); + let sectors_per_fat = (t1 + t2 - 1) / t2; + // Note: casting is safe here because number of sectors per FAT cannot be bigger than total sectors number + sectors_per_fat as u32 +} + +fn try_fs_geometry( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + fat_type: FatType, + root_dir_sectors: u32, + fats: u8, +) -> io::Result<(u16, u32)> { + // Note: most of implementations use 32 reserved sectors for FAT32 but it's wasting of space + // This implementation uses only 8. This is enough to fit in two boot sectors (main and backup) with additional + // bootstrap code and one FSInfo sector. It also makes FAT alligned to 4096 which is a nice number. + let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 8 } else { 1 }; + + // Check if volume has enough space to accomodate reserved sectors, FAT, root directory and some data space + // Having less than 8 sectors for FAT and data would make a little sense + if total_sectors <= u32::from(reserved_sectors) + u32::from(root_dir_sectors) + 8 { + return Err(Error::new(ErrorKind::Other, "Volume is too small")); + } + + // calculate File Allocation Table size + let sectors_per_fat = determine_sectors_per_fat( + total_sectors, + bytes_per_sector, + sectors_per_cluster, + fat_type, + reserved_sectors, + root_dir_sectors, + fats, + ); + + let data_sectors = + total_sectors - u32::from(reserved_sectors) - u32::from(root_dir_sectors) - sectors_per_fat * u32::from(fats); + let total_clusters = data_sectors / u32::from(sectors_per_cluster); + if fat_type != FatType::from_clusters(total_clusters) { + return Err(Error::new(ErrorKind::Other, "Invalid FAT type")); + } + debug_assert!(total_clusters >= fat_type.min_clusters()); + if total_clusters > fat_type.max_clusters() { + // Note: it can happen for FAT32 + return Err(Error::new(ErrorKind::Other, "Too many clusters")); + } + + return Ok((reserved_sectors, sectors_per_fat)); +} + +fn determine_root_dir_sectors(root_dir_entries: u16, bytes_per_sector: u16, fat_type: FatType) -> u32 { + if fat_type == FatType::Fat32 { + 0 + } else { + let root_dir_bytes = u32::from(root_dir_entries) * DIR_ENTRY_SIZE as u32; + (root_dir_bytes + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector) + } +} + +fn determine_fs_geometry( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + root_dir_entries: u16, + fats: u8, +) -> io::Result<(FatType, u16, u32)> { + for &fat_type in &[FatType::Fat32, FatType::Fat16, FatType::Fat12] { + let root_dir_sectors = determine_root_dir_sectors(root_dir_entries, bytes_per_sector, fat_type); + let result = + try_fs_geometry(total_sectors, bytes_per_sector, sectors_per_cluster, fat_type, root_dir_sectors, fats); + if result.is_ok() { + let (reserved_sectors, sectors_per_fat) = result.unwrap(); // SAFE: used is_ok() before + return Ok((fat_type, reserved_sectors, sectors_per_fat)); + } + } + + return Err(Error::new(ErrorKind::Other, "Cannot select FAT type - unfortunate disk size")); +} + +fn format_bpb( + options: &FormatVolumeOptions, + total_sectors: u32, + bytes_per_sector: u16, +) -> io::Result<(BiosParameterBlock, FatType)> { + let bytes_per_cluster = options.bytes_per_cluster.unwrap_or_else(|| { + let total_bytes = u64::from(total_sectors) * u64::from(bytes_per_sector); + determine_bytes_per_cluster(total_bytes, bytes_per_sector, options.fat_type) + }); + + let sectors_per_cluster = bytes_per_cluster / u32::from(bytes_per_sector); + assert!(sectors_per_cluster <= u32::from(u8::MAX)); + let sectors_per_cluster = sectors_per_cluster as u8; + + let fats = options.fats.unwrap_or(2u8); + let root_dir_entries = options.max_root_dir_entries.unwrap_or(512); + let (fat_type, reserved_sectors, sectors_per_fat) = + determine_fs_geometry(total_sectors, bytes_per_sector, sectors_per_cluster, root_dir_entries, fats)?; + + // drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type + let drive_num = options.drive_num.unwrap_or_else(|| if fat_type == FatType::Fat12 { 0 } else { 0x80 }); + + // reserved_0 is always zero + let reserved_0 = [0u8; 12]; + + // setup volume label + let mut volume_label = [0u8; 11]; + if let Some(volume_label_from_opts) = options.volume_label { + volume_label.copy_from_slice(&volume_label_from_opts); + } else { + volume_label.copy_from_slice(b"NO NAME "); + } + + // setup fs_type_label field + let mut fs_type_label = [0u8; 8]; + let fs_type_label_str = match fat_type { + FatType::Fat12 => b"FAT12 ", + FatType::Fat16 => b"FAT16 ", + FatType::Fat32 => b"FAT32 ", + }; + fs_type_label.copy_from_slice(fs_type_label_str); + + // create Bios Parameter Block struct + let is_fat32 = fat_type == FatType::Fat32; + let sectors_per_fat_16 = if is_fat32 { + 0 + } else { + debug_assert!(sectors_per_fat <= u32::from(u16::MAX)); + sectors_per_fat as u16 + }; + let bpb = BiosParameterBlock { + bytes_per_sector, + sectors_per_cluster, + reserved_sectors, + fats, + root_entries: if is_fat32 { 0 } else { root_dir_entries }, + total_sectors_16: if total_sectors < 0x10000 { total_sectors as u16 } else { 0 }, + media: options.media.unwrap_or(0xF8), + sectors_per_fat_16, + sectors_per_track: options.sectors_per_track.unwrap_or(0x20), + heads: options.heads.unwrap_or(0x40), + hidden_sectors: 0, + total_sectors_32: if total_sectors >= 0x10000 { total_sectors } else { 0 }, + // FAT32 fields start + sectors_per_fat_32: if is_fat32 { sectors_per_fat } else { 0 }, + extended_flags: 0, // mirroring enabled + fs_version: 0, + root_dir_first_cluster: if is_fat32 { 2 } else { 0 }, + fs_info_sector: if is_fat32 { 1 } else { 0 }, + backup_boot_sector: if is_fat32 { 6 } else { 0 }, + reserved_0, + // FAT32 fields end + drive_num, + reserved_1: 0, + ext_sig: 0x29, + volume_id: options.volume_id.unwrap_or(0x12345678), + volume_label, + fs_type_label, + }; + + // Check if number of clusters is proper for used FAT type + if FatType::from_clusters(bpb.total_clusters()) != fat_type { + return Err(Error::new( + ErrorKind::Other, + "Total number of clusters and FAT type does not match. Try other volume size", + )); + } + + Ok((bpb, fat_type)) +} + +pub(crate) fn format_boot_sector( + options: &FormatVolumeOptions, + total_sectors: u32, + bytes_per_sector: u16, +) -> io::Result<(BootSector, FatType)> { + let mut boot: BootSector = Default::default(); + let (bpb, fat_type) = format_bpb(options, total_sectors, bytes_per_sector)?; + boot.bpb = bpb; + boot.oem_name.copy_from_slice(b"MSWIN4.1"); + // Boot code copied from FAT32 boot sector initialized by mkfs.fat + boot.bootjmp = [0xEB, 0x58, 0x90]; + let boot_code: [u8; 129] = [ + 0x0E, 0x1F, 0xBE, 0x77, 0x7C, 0xAC, 0x22, 0xC0, 0x74, 0x0B, 0x56, 0xB4, 0x0E, 0xBB, 0x07, 0x00, 0xCD, 0x10, + 0x5E, 0xEB, 0xF0, 0x32, 0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54, 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, + 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, + 0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, + 0x20, 0x61, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, + 0x20, 0x61, 0x6E, 0x64, 0x0D, 0x0A, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, + 0x79, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, 0x69, 0x6E, 0x20, 0x2E, 0x2E, 0x2E, + 0x20, 0x0D, 0x0A, + ]; + boot.boot_code[..boot_code.len()].copy_from_slice(&boot_code); + boot.boot_sig = [0x55, 0xAA]; + + // fix offsets in bootjmp and boot code for non-FAT32 filesystems (bootcode is on a different offset) + if fat_type != FatType::Fat32 { + // offset of boot code + let boot_code_offset: u8 = 0x36 + 8; + boot.bootjmp[1] = boot_code_offset - 2; + // offset of message + const MESSAGE_OFFSET: u16 = 29; + let message_offset_in_sector = u16::from(boot_code_offset) + MESSAGE_OFFSET + 0x7c00; + boot.boot_code[3] = (message_offset_in_sector & 0xff) as u8; + boot.boot_code[4] = (message_offset_in_sector >> 8) as u8; + } + + Ok((boot, fat_type)) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_estimate_fat_type() { + assert_eq!(estimate_fat_type(3 * MB), FatType::Fat12); + assert_eq!(estimate_fat_type(4 * MB), FatType::Fat16); + assert_eq!(estimate_fat_type(511 * MB), FatType::Fat16); + assert_eq!(estimate_fat_type(512 * MB), FatType::Fat32); + } + + #[test] + fn test_determine_bytes_per_cluster_fat12() { + assert_eq!(determine_bytes_per_cluster(1 * MB + 0, 512, Some(FatType::Fat12)), 512); + assert_eq!(determine_bytes_per_cluster(1 * MB + 1, 512, Some(FatType::Fat12)), 1024); + assert_eq!(determine_bytes_per_cluster(1 * MB, 4096, Some(FatType::Fat12)), 4096); + } + + #[test] + fn test_determine_bytes_per_cluster_fat16() { + assert_eq!(determine_bytes_per_cluster(1 * MB, 512, Some(FatType::Fat16)), 1 * KB as u32); + assert_eq!(determine_bytes_per_cluster(1 * MB, 4 * KB as u16, Some(FatType::Fat16)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(16 * MB + 0, 512, Some(FatType::Fat16)), 1 * KB as u32); + assert_eq!(determine_bytes_per_cluster(16 * MB + 1, 512, Some(FatType::Fat16)), 2 * KB as u32); + assert_eq!(determine_bytes_per_cluster(128 * MB + 0, 512, Some(FatType::Fat16)), 2 * KB as u32); + assert_eq!(determine_bytes_per_cluster(128 * MB + 1, 512, Some(FatType::Fat16)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(256 * MB + 0, 512, Some(FatType::Fat16)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(256 * MB + 1, 512, Some(FatType::Fat16)), 8 * KB as u32); + assert_eq!(determine_bytes_per_cluster(512 * MB + 0, 512, Some(FatType::Fat16)), 8 * KB as u32); + assert_eq!(determine_bytes_per_cluster(512 * MB + 1, 512, Some(FatType::Fat16)), 16 * KB as u32); + assert_eq!(determine_bytes_per_cluster(1024 * MB + 0, 512, Some(FatType::Fat16)), 16 * KB as u32); + assert_eq!(determine_bytes_per_cluster(1024 * MB + 1, 512, Some(FatType::Fat16)), 32 * KB as u32); + assert_eq!(determine_bytes_per_cluster(99999 * MB, 512, Some(FatType::Fat16)), 32 * KB as u32); + } + + #[test] + fn test_determine_bytes_per_cluster_fat32() { + assert_eq!(determine_bytes_per_cluster(260 * MB as u64, 512, Some(FatType::Fat32)), 512); + assert_eq!(determine_bytes_per_cluster(260 * MB as u64, 4 * KB as u16, Some(FatType::Fat32)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(260 * MB as u64 + 1, 512, Some(FatType::Fat32)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(8 * GB as u64, 512, Some(FatType::Fat32)), 4 * KB as u32); + assert_eq!(determine_bytes_per_cluster(8 * GB as u64 + 1, 512, Some(FatType::Fat32)), 8 * KB as u32); + assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 0, 512, Some(FatType::Fat32)), 8 * KB as u32); + assert_eq!(determine_bytes_per_cluster(16 * GB as u64 + 1, 512, Some(FatType::Fat32)), 16 * KB as u32); + assert_eq!(determine_bytes_per_cluster(32 * GB as u64, 512, Some(FatType::Fat32)), 16 * KB as u32); + assert_eq!(determine_bytes_per_cluster(32 * GB as u64 + 1, 512, Some(FatType::Fat32)), 32 * KB as u32); + assert_eq!(determine_bytes_per_cluster(999 * GB as u64, 512, Some(FatType::Fat32)), 32 * KB as u32); + } + + fn test_determine_sectors_per_fat_single( + total_bytes: u64, + bytes_per_sector: u16, + bytes_per_cluster: u32, + fat_type: FatType, + reserved_sectors: u16, + fats: u8, + root_dir_entries: u32, + ) { + let total_sectors = total_bytes / u64::from(bytes_per_sector); + debug_assert!(total_sectors <= u64::from(core::u32::MAX), "{:x}", total_sectors); + let total_sectors = total_sectors as u32; + + let sectors_per_cluster = (bytes_per_cluster / u32::from(bytes_per_sector)) as u8; + let root_dir_size = root_dir_entries * DIR_ENTRY_SIZE as u32; + let root_dir_sectors = (root_dir_size + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector); + let sectors_per_fat = determine_sectors_per_fat( + total_sectors, + bytes_per_sector, + sectors_per_cluster, + fat_type, + reserved_sectors, + root_dir_sectors, + fats, + ); + + let sectors_per_all_fats = u32::from(fats) * sectors_per_fat; + let total_data_sectors = total_sectors - u32::from(reserved_sectors) - sectors_per_all_fats - root_dir_sectors; + let total_clusters = total_data_sectors / u32::from(sectors_per_cluster); + if FatType::from_clusters(total_clusters) != fat_type { + // Skip impossible FAT configurations + return; + } + let bits_per_sector = u32::from(bytes_per_sector) * BITS_PER_BYTE; + let bits_per_fat = u64::from(sectors_per_fat) * u64::from(bits_per_sector); + let total_fat_entries = (bits_per_fat / u64::from(fat_type.bits_per_fat_entry())) as u32; + let fat_clusters = total_fat_entries - RESERVED_FAT_ENTRIES; + // Note: fat_entries_per_sector is rounded down for FAT12 + let fat_entries_per_sector = u32::from(bits_per_sector) / fat_type.bits_per_fat_entry(); + let desc = format!("total_clusters {}, fat_clusters {}, total_sectors {}, bytes/sector {}, sectors/cluster {}, fat_type {:?}, reserved_sectors {}, root_dir_sectors {}, sectors_per_fat {}", + total_clusters, fat_clusters, total_sectors, bytes_per_sector, sectors_per_cluster, fat_type, reserved_sectors, root_dir_sectors, sectors_per_fat); + assert!(fat_clusters >= total_clusters, "Too small FAT: {}", desc); + assert!(fat_clusters <= total_clusters + 2 * fat_entries_per_sector, "Too big FAT: {}", desc); + } + + fn test_determine_sectors_per_fat_for_multiple_sizes( + bytes_per_sector: u16, + fat_type: FatType, + reserved_sectors: u16, + fats: u8, + root_dir_entries: u32, + ) { + let mut bytes_per_cluster = u32::from(bytes_per_sector); + while bytes_per_cluster <= 64 * KB as u32 { + let mut size = 1 * MB; + while size < 2048 * GB { + test_determine_sectors_per_fat_single( + size, + bytes_per_sector, + bytes_per_cluster, + fat_type, + reserved_sectors, + fats, + root_dir_entries, + ); + size = size + size / 7; + } + size = 2048 * GB - 1; + test_determine_sectors_per_fat_single( + size, + bytes_per_sector, + bytes_per_cluster, + fat_type, + reserved_sectors, + fats, + root_dir_entries, + ); + bytes_per_cluster *= 2; + } + } + + #[test] + fn test_determine_sectors_per_fat() { + let _ = env_logger::try_init(); + + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 2, 512); + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 1, 512); + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat12, 1, 2, 8192); + test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat12, 1, 2, 512); + + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 2, 512); + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 1, 512); + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat16, 1, 2, 8192); + test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat16, 1, 2, 512); + + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat32, 32, 2, 0); + test_determine_sectors_per_fat_for_multiple_sizes(512, FatType::Fat32, 32, 1, 0); + test_determine_sectors_per_fat_for_multiple_sizes(4096, FatType::Fat32, 32, 2, 0); + } + + #[test] + fn test_format_boot_sector() { + let _ = env_logger::try_init(); + let bytes_per_sector = 512u16; + // test all partition sizes from 1MB to 2TB (u32::MAX sectors is 2TB - 1 for 512 byte sectors) + let mut total_sectors_vec = Vec::new(); + let mut size = 1 * MB; + while size < 2048 * GB { + total_sectors_vec.push((size / u64::from(bytes_per_sector)) as u32); + size = size + size / 7; + } + total_sectors_vec.push(core::u32::MAX); + for total_sectors in total_sectors_vec { + let (boot, _) = format_boot_sector(&FormatVolumeOptions::new(), total_sectors, bytes_per_sector) + .expect("format_boot_sector"); + boot.validate().expect("validate"); + } + } +} diff --git a/src/byteorder_core_io.rs b/src/byteorder_core_io.rs new file mode 100644 index 0000000..2609454 --- /dev/null +++ b/src/byteorder_core_io.rs @@ -0,0 +1,1232 @@ +use core::slice; +use io::{self, Result}; + +use byteorder::ByteOrder; + +/// Extends `Read` with methods for reading numbers. (For `std::io`.) +/// +/// Most of the methods defined here have an unconstrained type parameter that +/// must be explicitly instantiated. Typically, it is instantiated with either +/// the `BigEndian` or `LittleEndian` types defined in this crate. +/// +/// # Examples +/// +/// Read unsigned 16 bit big-endian integers from a `Read`: +/// +/// ```rust +/// use std::io::Cursor; +/// use byteorder::{BigEndian, ReadBytesExt}; +/// +/// let mut rdr = Cursor::new(vec![2, 5, 3, 0]); +/// assert_eq!(517, rdr.read_u16::().unwrap()); +/// assert_eq!(768, rdr.read_u16::().unwrap()); +/// ``` +pub trait ReadBytesExt: io::Read { + /// Reads an unsigned 8 bit integer from the underlying reader. + /// + /// Note that since this reads a single byte, no byte order conversions + /// are used. It is included for completeness. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read unsigned 8 bit integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![2, 5]); + /// assert_eq!(2, rdr.read_u8().unwrap()); + /// assert_eq!(5, rdr.read_u8().unwrap()); + /// ``` + #[inline] + fn read_u8(&mut self) -> Result { + let mut buf = [0; 1]; + try!(self.read_exact(&mut buf)); + Ok(buf[0]) + } + + /// Reads a signed 8 bit integer from the underlying reader. + /// + /// Note that since this reads a single byte, no byte order conversions + /// are used. It is included for completeness. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read unsigned 8 bit integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x02, 0xfb]); + /// assert_eq!(2, rdr.read_i8().unwrap()); + /// assert_eq!(-5, rdr.read_i8().unwrap()); + /// ``` + #[inline] + fn read_i8(&mut self) -> Result { + let mut buf = [0; 1]; + try!(self.read_exact(&mut buf)); + Ok(buf[0] as i8) + } + + /// Reads an unsigned 16 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read unsigned 16 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![2, 5, 3, 0]); + /// assert_eq!(517, rdr.read_u16::().unwrap()); + /// assert_eq!(768, rdr.read_u16::().unwrap()); + /// ``` + #[inline] + fn read_u16(&mut self) -> Result { + let mut buf = [0; 2]; + try!(self.read_exact(&mut buf)); + Ok(T::read_u16(&buf)) + } + + /// Reads a signed 16 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read signed 16 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x00, 0xc1, 0xff, 0x7c]); + /// assert_eq!(193, rdr.read_i16::().unwrap()); + /// assert_eq!(-132, rdr.read_i16::().unwrap()); + /// ``` + #[inline] + fn read_i16(&mut self) -> Result { + let mut buf = [0; 2]; + try!(self.read_exact(&mut buf)); + Ok(T::read_i16(&buf)) + } + + /// Reads an unsigned 24 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read unsigned 24 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x00, 0x01, 0x0b]); + /// assert_eq!(267, rdr.read_u24::().unwrap()); + /// ``` + #[inline] + fn read_u24(&mut self) -> Result { + let mut buf = [0; 3]; + try!(self.read_exact(&mut buf)); + Ok(T::read_u24(&buf)) + } + + /// Reads a signed 24 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read signed 24 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0xff, 0x7a, 0x33]); + /// assert_eq!(-34253, rdr.read_i24::().unwrap()); + /// ``` + #[inline] + fn read_i24(&mut self) -> Result { + let mut buf = [0; 3]; + try!(self.read_exact(&mut buf)); + Ok(T::read_i24(&buf)) + } + + /// Reads an unsigned 32 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read unsigned 32 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x00, 0x00, 0x01, 0x0b]); + /// assert_eq!(267, rdr.read_u32::().unwrap()); + /// ``` + #[inline] + fn read_u32(&mut self) -> Result { + let mut buf = [0; 4]; + try!(self.read_exact(&mut buf)); + Ok(T::read_u32(&buf)) + } + + /// Reads a signed 32 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read signed 32 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0xff, 0xff, 0x7a, 0x33]); + /// assert_eq!(-34253, rdr.read_i32::().unwrap()); + /// ``` + #[inline] + fn read_i32(&mut self) -> Result { + let mut buf = [0; 4]; + try!(self.read_exact(&mut buf)); + Ok(T::read_i32(&buf)) + } + + /// Reads an unsigned 64 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read an unsigned 64 bit big-endian integer from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x00, 0x03, 0x43, 0x95, 0x4d, 0x60, 0x86, 0x83]); + /// assert_eq!(918733457491587, rdr.read_u64::().unwrap()); + /// ``` + #[inline] + fn read_u64(&mut self) -> Result { + let mut buf = [0; 8]; + try!(self.read_exact(&mut buf)); + Ok(T::read_u64(&buf)) + } + + /// Reads a signed 64 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a signed 64 bit big-endian integer from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x80, 0, 0, 0, 0, 0, 0, 0]); + /// assert_eq!(i64::min_value(), rdr.read_i64::().unwrap()); + /// ``` + #[inline] + fn read_i64(&mut self) -> Result { + let mut buf = [0; 8]; + try!(self.read_exact(&mut buf)); + Ok(T::read_i64(&buf)) + } + + /// Reads an unsigned 128 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read an unsigned 128 bit big-endian integer from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x00, 0x03, 0x43, 0x95, 0x4d, 0x60, 0x86, 0x83, + /// 0x00, 0x03, 0x43, 0x95, 0x4d, 0x60, 0x86, 0x83 + /// ]); + /// assert_eq!(16947640962301618749969007319746179, rdr.read_u128::().unwrap()); + /// ``` + #[cfg(feature = "i128")] + #[inline] + fn read_u128(&mut self) -> Result { + let mut buf = [0; 16]; + try!(self.read_exact(&mut buf)); + Ok(T::read_u128(&buf)) + } + + /// Reads a signed 128 bit integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a signed 128 bit big-endian integer from a `Read`: + /// + /// ```rust + /// #![feature(i128_type)] + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + /// assert_eq!(i128::min_value(), rdr.read_i128::().unwrap()); + /// ``` + #[cfg(feature = "i128")] + #[inline] + fn read_i128(&mut self) -> Result { + let mut buf = [0; 16]; + try!(self.read_exact(&mut buf)); + Ok(T::read_i128(&buf)) + } + + /// Reads an unsigned n-bytes integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read an unsigned n-byte big-endian integer from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0x80, 0x74, 0xfa]); + /// assert_eq!(8418554, rdr.read_uint::(3).unwrap()); + #[inline] + fn read_uint(&mut self, nbytes: usize) -> Result { + let mut buf = [0; 8]; + try!(self.read_exact(&mut buf[..nbytes])); + Ok(T::read_uint(&buf[..nbytes], nbytes)) + } + + /// Reads a signed n-bytes integer from the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read an unsigned n-byte big-endian integer from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0xc1, 0xff, 0x7c]); + /// assert_eq!(-4063364, rdr.read_int::(3).unwrap()); + #[inline] + fn read_int(&mut self, nbytes: usize) -> Result { + let mut buf = [0; 8]; + try!(self.read_exact(&mut buf[..nbytes])); + Ok(T::read_int(&buf[..nbytes], nbytes)) + } + + /// Reads an unsigned n-bytes integer from the underlying reader. + #[cfg(feature = "i128")] + #[inline] + fn read_uint128(&mut self, nbytes: usize) -> Result { + let mut buf = [0; 16]; + try!(self.read_exact(&mut buf[..nbytes])); + Ok(T::read_uint128(&buf[..nbytes], nbytes)) + } + + /// Reads a signed n-bytes integer from the underlying reader. + #[cfg(feature = "i128")] + #[inline] + fn read_int128(&mut self, nbytes: usize) -> Result { + let mut buf = [0; 16]; + try!(self.read_exact(&mut buf[..nbytes])); + Ok(T::read_int128(&buf[..nbytes], nbytes)) + } + + /// Reads a IEEE754 single-precision (4 bytes) floating point number from + /// the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a big-endian single-precision floating point number from a `Read`: + /// + /// ```rust + /// use std::f32; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x49, 0x0f, 0xdb, + /// ]); + /// assert_eq!(f32::consts::PI, rdr.read_f32::().unwrap()); + /// ``` + #[inline] + fn read_f32(&mut self) -> Result { + let mut buf = [0; 4]; + try!(self.read_exact(&mut buf)); + Ok(T::read_f32(&buf)) + } + + /// Reads a IEEE754 double-precision (8 bytes) floating point number from + /// the underlying reader. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a big-endian double-precision floating point number from a `Read`: + /// + /// ```rust + /// use std::f64; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18, + /// ]); + /// assert_eq!(f64::consts::PI, rdr.read_f64::().unwrap()); + /// ``` + #[inline] + fn read_f64(&mut self) -> Result { + let mut buf = [0; 8]; + try!(self.read_exact(&mut buf)); + Ok(T::read_f64(&buf)) + } + + /// Reads a sequence of unsigned 16 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of unsigned 16 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![2, 5, 3, 0]); + /// let mut dst = [0; 2]; + /// rdr.read_u16_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_u16_into(&mut self, dst: &mut [u16]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_u16(dst); + Ok(()) + } + + /// Reads a sequence of unsigned 32 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of unsigned 32 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0, 0, 2, 5, 0, 0, 3, 0]); + /// let mut dst = [0; 2]; + /// rdr.read_u32_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_u32_into(&mut self, dst: &mut [u32]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_u32(dst); + Ok(()) + } + + /// Reads a sequence of unsigned 64 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of unsigned 64 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0, 0, 0, 0, 0, 0, 2, 5, + /// 0, 0, 0, 0, 0, 0, 3, 0, + /// ]); + /// let mut dst = [0; 2]; + /// rdr.read_u64_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_u64_into(&mut self, dst: &mut [u64]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_u64(dst); + Ok(()) + } + + /// Reads a sequence of unsigned 128 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of unsigned 128 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, + /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + /// ]); + /// let mut dst = [0; 2]; + /// rdr.read_u128_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[cfg(feature = "i128")] + #[inline] + fn read_u128_into(&mut self, dst: &mut [u128]) -> Result<()> { + { + let mut buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_u128(dst); + Ok(()) + } + + /// Reads a sequence of signed 16 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of signed 16 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![2, 5, 3, 0]); + /// let mut dst = [0; 2]; + /// rdr.read_i16_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_i16_into(&mut self, dst: &mut [i16]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_i16(dst); + Ok(()) + } + + /// Reads a sequence of signed 32 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of signed 32 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![0, 0, 2, 5, 0, 0, 3, 0]); + /// let mut dst = [0; 2]; + /// rdr.read_i32_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_i32_into(&mut self, dst: &mut [i32]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_i32(dst); + Ok(()) + } + + /// Reads a sequence of signed 64 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of signed 64 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0, 0, 0, 0, 0, 0, 2, 5, + /// 0, 0, 0, 0, 0, 0, 3, 0, + /// ]); + /// let mut dst = [0; 2]; + /// rdr.read_i64_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[inline] + fn read_i64_into(&mut self, dst: &mut [i64]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_i64(dst); + Ok(()) + } + + /// Reads a sequence of signed 128 bit integers from the underlying + /// reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of signed 128 bit big-endian integers from a `Read`: + /// + /// ```rust + /// use std::io::Cursor; + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 5, + /// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, + /// ]); + /// let mut dst = [0; 2]; + /// rdr.read_i128_into::(&mut dst).unwrap(); + /// assert_eq!([517, 768], dst); + /// ``` + #[cfg(feature = "i128")] + #[inline] + fn read_i128_into(&mut self, dst: &mut [i128]) -> Result<()> { + { + let mut buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_i128(dst); + Ok(()) + } + + /// Reads a sequence of IEEE754 single-precision (4 bytes) floating + /// point numbers from the underlying reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of big-endian single-precision floating point number + /// from a `Read`: + /// + /// ```rust + /// use std::f32; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x49, 0x0f, 0xdb, + /// 0x3f, 0x80, 0x00, 0x00, + /// ]); + /// let mut dst = [0.0; 2]; + /// rdr.read_f32_into::(&mut dst).unwrap(); + /// assert_eq!([f32::consts::PI, 1.0], dst); + /// ``` + #[inline] + fn read_f32_into(&mut self, dst: &mut [f32]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_f32(dst); + Ok(()) + } + + /// **DEPRECATED**. + /// + /// This method is deprecated. Use `read_f32_into` instead. + /// + /// Reads a sequence of IEEE754 single-precision (4 bytes) floating + /// point numbers from the underlying reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of big-endian single-precision floating point number + /// from a `Read`: + /// + /// ```rust + /// use std::f32; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x49, 0x0f, 0xdb, + /// 0x3f, 0x80, 0x00, 0x00, + /// ]); + /// let mut dst = [0.0; 2]; + /// rdr.read_f32_into_unchecked::(&mut dst).unwrap(); + /// assert_eq!([f32::consts::PI, 1.0], dst); + /// ``` + #[inline] + #[deprecated(since = "1.2.0", note = "please use `read_f32_into` instead")] + fn read_f32_into_unchecked(&mut self, dst: &mut [f32]) -> Result<()> { + self.read_f32_into::(dst) + } + + /// Reads a sequence of IEEE754 double-precision (8 bytes) floating + /// point numbers from the underlying reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of big-endian single-precision floating point number + /// from a `Read`: + /// + /// ```rust + /// use std::f64; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18, + /// 0x3f, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// ]); + /// let mut dst = [0.0; 2]; + /// rdr.read_f64_into::(&mut dst).unwrap(); + /// assert_eq!([f64::consts::PI, 1.0], dst); + /// ``` + #[inline] + fn read_f64_into(&mut self, dst: &mut [f64]) -> Result<()> { + { + let buf = unsafe { slice_to_u8_mut(dst) }; + try!(self.read_exact(buf)); + } + T::from_slice_f64(dst); + Ok(()) + } + + /// **DEPRECATED**. + /// + /// This method is deprecated. Use `read_f64_into` instead. + /// + /// Reads a sequence of IEEE754 double-precision (8 bytes) floating + /// point numbers from the underlying reader. + /// + /// The given buffer is either filled completely or an error is returned. + /// If an error is returned, the contents of `dst` are unspecified. + /// + /// # Safety + /// + /// This method is unsafe because there are no guarantees made about the + /// floating point values. In particular, this method does not check for + /// signaling NaNs, which may result in undefined behavior. + /// + /// # Errors + /// + /// This method returns the same errors as [`Read::read_exact`]. + /// + /// [`Read::read_exact`]: https://doc.rust-lang.org/std/io/trait.Read.html#method.read_exact + /// + /// # Examples + /// + /// Read a sequence of big-endian single-precision floating point number + /// from a `Read`: + /// + /// ```rust + /// use std::f64; + /// use std::io::Cursor; + /// + /// use byteorder::{BigEndian, ReadBytesExt}; + /// + /// let mut rdr = Cursor::new(vec![ + /// 0x40, 0x09, 0x21, 0xfb, 0x54, 0x44, 0x2d, 0x18, + /// 0x3f, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /// ]); + /// let mut dst = [0.0; 2]; + /// rdr.read_f64_into_unchecked::(&mut dst).unwrap(); + /// assert_eq!([f64::consts::PI, 1.0], dst); + /// ``` + #[inline] + #[deprecated(since = "1.2.0", note = "please use `read_f64_into` instead")] + fn read_f64_into_unchecked(&mut self, dst: &mut [f64]) -> Result<()> { + self.read_f64_into::(dst) + } +} + +/// All types that implement `Read` get methods defined in `ReadBytesExt` +/// for free. +impl ReadBytesExt for R {} + +/// Extends `Write` with methods for writing numbers. (For `std::io`.) +/// +/// Most of the methods defined here have an unconstrained type parameter that +/// must be explicitly instantiated. Typically, it is instantiated with either +/// the `BigEndian` or `LittleEndian` types defined in this crate. +/// +/// # Examples +/// +/// Write unsigned 16 bit big-endian integers to a `Write`: +/// +/// ```rust +/// use byteorder::{BigEndian, WriteBytesExt}; +/// +/// let mut wtr = vec![]; +/// wtr.write_u16::(517).unwrap(); +/// wtr.write_u16::(768).unwrap(); +/// assert_eq!(wtr, vec![2, 5, 3, 0]); +/// ``` +pub trait WriteBytesExt: io::Write { + /// Writes an unsigned 8 bit integer to the underlying writer. + /// + /// Note that since this writes a single byte, no byte order conversions + /// are used. It is included for completeness. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_u8(&mut self, n: u8) -> Result<()> { + self.write_all(&[n]) + } + + /// Writes a signed 8 bit integer to the underlying writer. + /// + /// Note that since this writes a single byte, no byte order conversions + /// are used. It is included for completeness. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_i8(&mut self, n: i8) -> Result<()> { + self.write_all(&[n as u8]) + } + + /// Writes an unsigned 16 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_u16(&mut self, n: u16) -> Result<()> { + let mut buf = [0; 2]; + T::write_u16(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a signed 16 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_i16(&mut self, n: i16) -> Result<()> { + let mut buf = [0; 2]; + T::write_i16(&mut buf, n); + self.write_all(&buf) + } + + /// Writes an unsigned 24 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_u24(&mut self, n: u32) -> Result<()> { + let mut buf = [0; 3]; + T::write_u24(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a signed 24 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_i24(&mut self, n: i32) -> Result<()> { + let mut buf = [0; 3]; + T::write_i24(&mut buf, n); + self.write_all(&buf) + } + + /// Writes an unsigned 32 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_u32(&mut self, n: u32) -> Result<()> { + let mut buf = [0; 4]; + T::write_u32(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a signed 32 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_i32(&mut self, n: i32) -> Result<()> { + let mut buf = [0; 4]; + T::write_i32(&mut buf, n); + self.write_all(&buf) + } + + /// Writes an unsigned 64 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_u64(&mut self, n: u64) -> Result<()> { + let mut buf = [0; 8]; + T::write_u64(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a signed 64 bit integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_i64(&mut self, n: i64) -> Result<()> { + let mut buf = [0; 8]; + T::write_i64(&mut buf, n); + self.write_all(&buf) + } + + /// Writes an unsigned 128 bit integer to the underlying writer. + #[cfg(feature = "i128")] + #[inline] + fn write_u128(&mut self, n: u128) -> Result<()> { + let mut buf = [0; 16]; + T::write_u128(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a signed 128 bit integer to the underlying writer. + #[cfg(feature = "i128")] + #[inline] + fn write_i128(&mut self, n: i128) -> Result<()> { + let mut buf = [0; 16]; + T::write_i128(&mut buf, n); + self.write_all(&buf) + } + + /// Writes an unsigned n-bytes integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + /// + /// # Panics + /// + /// If the given integer is not representable in the given number of bytes, + /// this method panics. If `nbytes > 8`, this method panics. + #[inline] + fn write_uint(&mut self, n: u64, nbytes: usize) -> Result<()> { + let mut buf = [0; 8]; + T::write_uint(&mut buf, n, nbytes); + self.write_all(&buf[0..nbytes]) + } + + /// Writes a signed n-bytes integer to the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + /// + /// # Panics + /// + /// If the given integer is not representable in the given number of bytes, + /// this method panics. If `nbytes > 8`, this method panics. + #[inline] + fn write_int(&mut self, n: i64, nbytes: usize) -> Result<()> { + let mut buf = [0; 8]; + T::write_int(&mut buf, n, nbytes); + self.write_all(&buf[0..nbytes]) + } + + /// Writes an unsigned n-bytes integer to the underlying writer. + /// + /// If the given integer is not representable in the given number of bytes, + /// this method panics. If `nbytes > 16`, this method panics. + #[cfg(feature = "i128")] + #[inline] + fn write_uint128(&mut self, n: u128, nbytes: usize) -> Result<()> { + let mut buf = [0; 16]; + T::write_uint128(&mut buf, n, nbytes); + self.write_all(&buf[0..nbytes]) + } + + /// Writes a signed n-bytes integer to the underlying writer. + /// + /// If the given integer is not representable in the given number of bytes, + /// this method panics. If `nbytes > 16`, this method panics. + #[cfg(feature = "i128")] + #[inline] + fn write_int128(&mut self, n: i128, nbytes: usize) -> Result<()> { + let mut buf = [0; 16]; + T::write_int128(&mut buf, n, nbytes); + self.write_all(&buf[0..nbytes]) + } + + /// Writes a IEEE754 single-precision (4 bytes) floating point number to + /// the underlying writer. + /// + /// # Errors + /// + /// This method returns the same errors as [`Write::write_all`]. + /// + /// [`Write::write_all`]: https://doc.rust-lang.org/std/io/trait.Write.html#method.write_all + #[inline] + fn write_f32(&mut self, n: f32) -> Result<()> { + let mut buf = [0; 4]; + T::write_f32(&mut buf, n); + self.write_all(&buf) + } + + /// Writes a IEEE754 double-precision (8 bytes) floating point number to + /// the underlying writer. + #[inline] + fn write_f64(&mut self, n: f64) -> Result<()> { + let mut buf = [0; 8]; + T::write_f64(&mut buf, n); + self.write_all(&buf) + } +} + +/// All types that implement `Write` get methods defined in `WriteBytesExt` +/// for free. +impl WriteBytesExt for W {} + +/// Convert a slice of T (where T is plain old data) to its mutable binary +/// representation. +/// +/// This function is wildly unsafe because it permits arbitrary modification of +/// the binary representation of any `Copy` type. Use with care. +unsafe fn slice_to_u8_mut(slice: &mut [T]) -> &mut [u8] { + use core::mem::size_of; + + let len = size_of::() * slice.len(); + slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, len) +} diff --git a/src/dir.rs b/src/dir.rs new file mode 100644 index 0000000..051f11c --- /dev/null +++ b/src/dir.rs @@ -0,0 +1,1076 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::vec::Vec; +use core::{char, cmp, num, str}; +#[cfg(feature = "alloc")] +use core::{iter, slice}; +use io; +use io::prelude::*; +use io::{ErrorKind, SeekFrom}; + +use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, ShortName, DIR_ENTRY_SIZE}; +#[cfg(feature = "alloc")] +use dir_entry::{LFN_ENTRY_LAST_FLAG, LFN_PART_LEN}; +use file::File; +use fs::{DiskSlice, FileSystem, FsIoAdapter, ReadWriteSeek}; + +#[cfg(feature = "alloc")] +type LfnUtf16 = Vec; +#[cfg(not(feature = "alloc"))] +type LfnUtf16 = (); + +const SFN_PADDING: u8 = 0x20; + +pub(crate) enum DirRawStream<'a, T: ReadWriteSeek + 'a> { + File(File<'a, T>), + Root(DiskSlice>), +} + +impl<'a, T: ReadWriteSeek> DirRawStream<'a, T> { + fn abs_pos(&self) -> Option { + match self { + &DirRawStream::File(ref file) => file.abs_pos(), + &DirRawStream::Root(ref slice) => Some(slice.abs_pos()), + } + } + + fn first_cluster(&self) -> Option { + match self { + &DirRawStream::File(ref file) => file.first_cluster(), + &DirRawStream::Root(_) => None, + } + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl<'a, T: ReadWriteSeek> Clone for DirRawStream<'a, T> { + fn clone(&self) -> Self { + match self { + &DirRawStream::File(ref file) => DirRawStream::File(file.clone()), + &DirRawStream::Root(ref raw) => DirRawStream::Root(raw.clone()), + } + } +} + +impl<'a, T: ReadWriteSeek> Read for DirRawStream<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self { + &mut DirRawStream::File(ref mut file) => file.read(buf), + &mut DirRawStream::Root(ref mut raw) => raw.read(buf), + } + } +} + +impl<'a, T: ReadWriteSeek> Write for DirRawStream<'a, T> { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + &mut DirRawStream::File(ref mut file) => file.write(buf), + &mut DirRawStream::Root(ref mut raw) => raw.write(buf), + } + } + fn flush(&mut self) -> io::Result<()> { + match self { + &mut DirRawStream::File(ref mut file) => file.flush(), + &mut DirRawStream::Root(ref mut raw) => raw.flush(), + } + } +} + +impl<'a, T: ReadWriteSeek> Seek for DirRawStream<'a, T> { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + match self { + &mut DirRawStream::File(ref mut file) => file.seek(pos), + &mut DirRawStream::Root(ref mut raw) => raw.seek(pos), + } + } +} + +fn split_path<'c>(path: &'c str) -> (&'c str, Option<&'c str>) { + // remove trailing slash and split into 2 components - top-most parent and rest + let mut path_split = path.trim_matches('/').splitn(2, '/'); + let comp = path_split.next().unwrap(); // SAFE: splitn always returns at least one element + let rest_opt = path_split.next(); + (comp, rest_opt) +} + +enum DirEntryOrShortName<'a, T: ReadWriteSeek + 'a> { + DirEntry(DirEntry<'a, T>), + ShortName([u8; 11]), +} + +/// A FAT filesystem directory. +/// +/// This struct is created by the `open_dir` or `create_dir` methods on `Dir`. +/// The root directory is returned by the `root_dir` method on `FileSystem`. +pub struct Dir<'a, T: ReadWriteSeek + 'a> { + stream: DirRawStream<'a, T>, + fs: &'a FileSystem, +} + +impl<'a, T: ReadWriteSeek + 'a> Dir<'a, T> { + pub(crate) fn new(stream: DirRawStream<'a, T>, fs: &'a FileSystem) -> Self { + Dir { stream, fs } + } + + /// Creates directory entries iterator. + pub fn iter(&self) -> DirIter<'a, T> { + DirIter::new(self.stream.clone(), self.fs, true) + } + + fn find_entry( + &self, + name: &str, + is_dir: Option, + mut short_name_gen: Option<&mut ShortNameGenerator>, + ) -> io::Result> { + for r in self.iter() { + let e = r?; + // compare name ignoring case + if e.eq_name(name) { + // check if file or directory is expected + if is_dir.is_some() && Some(e.is_dir()) != is_dir { + let error_msg = if e.is_dir() { "Is a directory" } else { "Not a directory" }; + return Err(io::Error::new(ErrorKind::Other, error_msg)); + } + return Ok(e); + } + // update short name generator state + if let Some(ref mut gen) = short_name_gen { + gen.add_existing(e.raw_short_name()); + } + } + Err(io::Error::new(ErrorKind::NotFound, "No such file or directory")) + } + + pub(crate) fn find_volume_entry(&self) -> io::Result>> { + for r in DirIter::new(self.stream.clone(), self.fs, false) { + let e = r?; + if e.data.is_volume() { + return Ok(Some(e)); + } + } + Ok(None) + } + + fn check_for_existence(&self, name: &str, is_dir: Option) -> io::Result> { + let mut short_name_gen = ShortNameGenerator::new(name); + loop { + let r = self.find_entry(name, is_dir, Some(&mut short_name_gen)); + match r { + Err(ref err) if err.kind() == ErrorKind::NotFound => {}, + // other error + Err(err) => return Err(err), + // directory already exists - return it + Ok(e) => return Ok(DirEntryOrShortName::DirEntry(e)), + }; + if let Ok(name) = short_name_gen.generate() { + return Ok(DirEntryOrShortName::ShortName(name)); + } + short_name_gen.next_iteration(); + } + } + + /// Opens existing subdirectory. + /// + /// `path` is a '/' separated directory path relative to self directory. + pub fn open_dir(&self, path: &str) -> io::Result { + trace!("open_dir {}", path); + let (name, rest_opt) = split_path(path); + let e = self.find_entry(name, Some(true), None)?; + match rest_opt { + Some(rest) => e.to_dir().open_dir(rest), + None => Ok(e.to_dir()), + } + } + + /// Opens existing file. + /// + /// `path` is a '/' separated file path relative to self directory. + pub fn open_file(&self, path: &str) -> io::Result> { + trace!("open_file {}", path); + // traverse path + let (name, rest_opt) = split_path(path); + if let Some(rest) = rest_opt { + let e = self.find_entry(name, Some(true), None)?; + return e.to_dir().open_file(rest); + } + // convert entry to a file + let e = self.find_entry(name, Some(false), None)?; + Ok(e.to_file()) + } + + /// Creates new or opens existing file=. + /// + /// `path` is a '/' separated file path relative to self directory. + /// File is never truncated when opening. It can be achieved by calling `File::truncate` method after opening. + pub fn create_file(&self, path: &str) -> io::Result> { + trace!("create_file {}", path); + // traverse path + let (name, rest_opt) = split_path(path); + if let Some(rest) = rest_opt { + return self.find_entry(name, Some(true), None)?.to_dir().create_file(rest); + } + // this is final filename in the path + let r = self.check_for_existence(name, Some(false))?; + match r { + // file does not exist - create it + DirEntryOrShortName::ShortName(short_name) => { + let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::from_bits_truncate(0), None); + Ok(self.write_entry(name, sfn_entry)?.to_file()) + }, + // file already exists - return it + DirEntryOrShortName::DirEntry(e) => Ok(e.to_file()), + } + } + + /// Creates new directory or opens existing. + /// + /// `path` is a '/' separated path relative to self directory. + pub fn create_dir(&self, path: &str) -> io::Result { + trace!("create_dir {}", path); + // traverse path + let (name, rest_opt) = split_path(path); + if let Some(rest) = rest_opt { + return self.find_entry(name, Some(true), None)?.to_dir().create_dir(rest); + } + // this is final filename in the path + let r = self.check_for_existence(name, Some(true))?; + match r { + // directory does not exist - create it + DirEntryOrShortName::ShortName(short_name) => { + // alloc cluster for directory data + let cluster = self.fs.alloc_cluster(None, true)?; + // create entry in parent directory + let sfn_entry = self.create_sfn_entry(short_name, FileAttributes::DIRECTORY, Some(cluster)); + let entry = self.write_entry(name, sfn_entry)?; + let dir = entry.to_dir(); + // create special entries "." and ".." + let dot_sfn = ShortNameGenerator::generate_dot(); + let sfn_entry = self.create_sfn_entry(dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster()); + dir.write_entry(".", sfn_entry)?; + let dotdot_sfn = ShortNameGenerator::generate_dotdot(); + let sfn_entry = + self.create_sfn_entry(dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster()); + dir.write_entry("..", sfn_entry)?; + Ok(dir) + }, + // directory already exists - return it + DirEntryOrShortName::DirEntry(e) => Ok(e.to_dir()), + } + } + + fn is_empty(&self) -> io::Result { + trace!("is_empty"); + // check if directory contains no files + for r in self.iter() { + let e = r?; + let name = e.short_file_name_as_bytes(); + // ignore special entries "." and ".." + if name != b"." && name != b".." { + return Ok(false); + } + } + Ok(true) + } + + /// Removes existing file or directory. + /// + /// `path` is a '/' separated file path relative to self directory. + /// Make sure there is no reference to this file (no File instance) or filesystem corruption + /// can happen. + pub fn remove(&self, path: &str) -> io::Result<()> { + trace!("remove {}", path); + // traverse path + let (name, rest_opt) = split_path(path); + if let Some(rest) = rest_opt { + let e = self.find_entry(name, Some(true), None)?; + return e.to_dir().remove(rest); + } + // in case of directory check if it is empty + let e = self.find_entry(name, None, None)?; + if e.is_dir() && !e.to_dir().is_empty()? { + return Err(io::Error::new(ErrorKind::Other, "Directory not empty")); + } + // free data + if let Some(n) = e.first_cluster() { + self.fs.free_cluster_chain(n)?; + } + // free long and short name entries + let mut stream = self.stream.clone(); + stream.seek(SeekFrom::Start(e.offset_range.0 as u64))?; + let num = (e.offset_range.1 - e.offset_range.0) as usize / DIR_ENTRY_SIZE as usize; + for _ in 0..num { + let mut data = DirEntryData::deserialize(&mut stream)?; + trace!("removing dir entry {:?}", data); + data.set_deleted(); + stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?; + data.serialize(&mut stream)?; + } + Ok(()) + } + + /// Renames or moves existing file or directory. + /// + /// `src_path` is a '/' separated source file path relative to self directory. + /// `dst_path` is a '/' separated destination file path relative to `dst_dir`. + /// `dst_dir` can be set to self directory if rename operation without moving is needed. + /// Make sure there is no reference to this file (no File instance) or filesystem corruption + /// can happen. + pub fn rename(&self, src_path: &str, dst_dir: &Dir, dst_path: &str) -> io::Result<()> { + trace!("rename {} {}", src_path, dst_path); + // traverse source path + let (name, rest_opt) = split_path(src_path); + if let Some(rest) = rest_opt { + let e = self.find_entry(name, Some(true), None)?; + return e.to_dir().rename(rest, dst_dir, dst_path); + } + // traverse destination path + let (name, rest_opt) = split_path(dst_path); + if let Some(rest) = rest_opt { + let e = dst_dir.find_entry(name, Some(true), None)?; + return self.rename(src_path, &e.to_dir(), rest); + } + // move/rename file + self.rename_internal(src_path, dst_dir, dst_path) + } + + fn rename_internal(&self, src_name: &str, dst_dir: &Dir, dst_name: &str) -> io::Result<()> { + trace!("rename_internal {} {}", src_name, dst_name); + // find existing file + let e = self.find_entry(src_name, None, None)?; + // check if destionation filename is unused + let r = dst_dir.check_for_existence(dst_name, None)?; + let short_name = match r { + DirEntryOrShortName::DirEntry(ref dst_e) => { + // check if source and destination entry is the same + if e.is_same_entry(dst_e) { + return Ok(()); + } + return Err(io::Error::new(ErrorKind::AlreadyExists, "Destination file already exists")); + }, + DirEntryOrShortName::ShortName(short_name) => short_name, + }; + // free long and short name entries + let mut stream = self.stream.clone(); + stream.seek(SeekFrom::Start(e.offset_range.0 as u64))?; + let num = (e.offset_range.1 - e.offset_range.0) as usize / DIR_ENTRY_SIZE as usize; + for _ in 0..num { + let mut data = DirEntryData::deserialize(&mut stream)?; + trace!("removing LFN entry {:?}", data); + data.set_deleted(); + stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?; + data.serialize(&mut stream)?; + } + // save new directory entry + let sfn_entry = e.data.renamed(short_name); + dst_dir.write_entry(dst_name, sfn_entry)?; + Ok(()) + } + + fn find_free_entries(&self, num_entries: usize) -> io::Result> { + let mut stream = self.stream.clone(); + let mut first_free = 0; + let mut num_free = 0; + let mut i = 0; + loop { + let raw_entry = DirEntryData::deserialize(&mut stream)?; + if raw_entry.is_end() { + // first unused entry - all remaining space can be used + if num_free == 0 { + first_free = i; + } + stream.seek(io::SeekFrom::Start(first_free as u64 * DIR_ENTRY_SIZE))?; + return Ok(stream); + } else if raw_entry.is_deleted() { + // free entry - calculate number of free entries in a row + if num_free == 0 { + first_free = i; + } + num_free += 1; + if num_free == num_entries { + // enough space for new file + stream.seek(io::SeekFrom::Start(first_free as u64 * DIR_ENTRY_SIZE))?; + return Ok(stream); + } + } else { + // used entry - start counting from 0 + num_free = 0; + } + i += 1; + } + } + + fn create_sfn_entry( + &self, + short_name: [u8; 11], + attrs: FileAttributes, + first_cluster: Option, + ) -> DirFileEntryData { + let mut raw_entry = DirFileEntryData::new(short_name, attrs); + raw_entry.set_first_cluster(first_cluster, self.fs.fat_type()); + let now = self.fs.options.time_provider.get_current_date_time(); + raw_entry.set_created(now); + raw_entry.set_accessed(now.date); + raw_entry.set_modified(now); + raw_entry + } + + #[cfg(feature = "alloc")] + fn encode_lfn_utf16(name: &str) -> Vec { + name.encode_utf16().collect::>() + } + #[cfg(not(feature = "alloc"))] + fn encode_lfn_utf16(_name: &str) -> () { + () + } + + fn alloc_and_write_lfn_entries( + &self, + lfn_utf16: &LfnUtf16, + short_name: &[u8; 11], + ) -> io::Result<(DirRawStream<'a, T>, u64)> { + // get short name checksum + let lfn_chsum = lfn_checksum(short_name); + // create LFN entries generator + let lfn_iter = LfnEntriesGenerator::new(&lfn_utf16, lfn_chsum); + // find space for new entries (multiple LFN entries and 1 SFN entry) + let num_entries = lfn_iter.len() + 1; + let mut stream = self.find_free_entries(num_entries)?; + let start_pos = stream.seek(io::SeekFrom::Current(0))?; + // write LFN entries before SFN entry + for lfn_entry in lfn_iter { + lfn_entry.serialize(&mut stream)?; + } + Ok((stream, start_pos)) + } + + fn write_entry(&self, name: &str, raw_entry: DirFileEntryData) -> io::Result> { + trace!("write_entry {}", name); + // check if name doesn't contain unsupported characters + validate_long_name(name)?; + // convert long name to UTF-16 + let lfn_utf16 = Self::encode_lfn_utf16(name); + // write LFN entries + let (mut stream, start_pos) = self.alloc_and_write_lfn_entries(&lfn_utf16, raw_entry.name())?; + // write short name entry + raw_entry.serialize(&mut stream)?; + let end_pos = stream.seek(io::SeekFrom::Current(0))?; + let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE); + // return new logical entry descriptor + let short_name = ShortName::new(raw_entry.name()); + Ok(DirEntry { + data: raw_entry, + short_name, + lfn_utf16, + fs: self.fs, + entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is absent only for empty file + offset_range: (start_pos, end_pos), + }) + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl<'a, T: ReadWriteSeek> Clone for Dir<'a, T> { + fn clone(&self) -> Self { + Self { stream: self.stream.clone(), fs: self.fs } + } +} + +/// An iterator over the directory entries. +/// +/// This struct is created by the `iter` method on `Dir`. +pub struct DirIter<'a, T: ReadWriteSeek + 'a> { + stream: DirRawStream<'a, T>, + fs: &'a FileSystem, + skip_volume: bool, + err: bool, +} + +impl<'a, T: ReadWriteSeek> DirIter<'a, T> { + fn new(stream: DirRawStream<'a, T>, fs: &'a FileSystem, skip_volume: bool) -> Self { + DirIter { stream, fs, skip_volume, err: false } + } + + fn should_ship_entry(&self, raw_entry: &DirEntryData) -> bool { + if raw_entry.is_deleted() { + return true; + } + match raw_entry { + &DirEntryData::File(ref sfn_entry) => self.skip_volume && sfn_entry.is_volume(), + _ => false, + } + } + + fn read_dir_entry(&mut self) -> io::Result>> { + trace!("read_dir_entry"); + let mut lfn_buf = LongNameBuilder::new(); + let mut offset = self.stream.seek(SeekFrom::Current(0))?; + let mut begin_offset = offset; + loop { + let raw_entry = DirEntryData::deserialize(&mut self.stream)?; + offset += DIR_ENTRY_SIZE; + // Check if this is end of dir + if raw_entry.is_end() { + return Ok(None); + } + // Check if this is deleted or volume ID entry + if self.should_ship_entry(&raw_entry) { + trace!("skip entry"); + lfn_buf.clear(); + begin_offset = offset; + continue; + } + match raw_entry { + DirEntryData::File(data) => { + // Get entry position on volume + let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE); + // Check if LFN checksum is valid + lfn_buf.validate_chksum(data.name()); + // Return directory entry + let short_name = ShortName::new(data.name()); + trace!("file entry {:?}", data.name()); + return Ok(Some(DirEntry { + data, + short_name, + lfn_utf16: lfn_buf.into_vec(), + fs: self.fs, + entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is empty only for empty file + offset_range: (begin_offset, offset), + })); + }, + DirEntryData::Lfn(data) => { + // Append to LFN buffer + trace!("lfn entry"); + lfn_buf.process(&data); + }, + } + } + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl<'a, T: ReadWriteSeek> Clone for DirIter<'a, T> { + fn clone(&self) -> Self { + Self { stream: self.stream.clone(), fs: self.fs, err: self.err, skip_volume: self.skip_volume } + } +} + +impl<'a, T: ReadWriteSeek> Iterator for DirIter<'a, T> { + type Item = io::Result>; + + fn next(&mut self) -> Option { + if self.err { + return None; + } + let r = self.read_dir_entry(); + match r { + Ok(Some(e)) => Some(Ok(e)), + Ok(None) => None, + Err(err) => { + self.err = true; + Some(Err(err)) + }, + } + } +} + +fn validate_long_name(name: &str) -> io::Result<()> { + // check if length is valid + if name.is_empty() { + return Err(io::Error::new(ErrorKind::Other, "File name is empty")); + } + if name.len() > 255 { + return Err(io::Error::new(ErrorKind::Other, "File name too long")); + } + // check if there are only valid characters + for c in name.chars() { + match c { + 'a'...'z' | 'A'...'Z' | '0'...'9' => {}, + '\u{80}'...'\u{FFFF}' => {}, + '$' | '%' | '\'' | '-' | '_' | '@' | '~' | '`' | '!' | '(' | ')' | '{' | '}' | '.' | ' ' | '+' | ',' + | ';' | '=' | '[' | ']' | '^' | '#' | '&' => {}, + _ => return Err(io::Error::new(ErrorKind::Other, "File name contains unsupported characters")), + } + } + Ok(()) +} + +fn lfn_checksum(short_name: &[u8; 11]) -> u8 { + let mut chksum = num::Wrapping(0u8); + for b in short_name { + chksum = (chksum << 7) + (chksum >> 1) + num::Wrapping(*b); + } + chksum.0 +} + +#[cfg(feature = "alloc")] +struct LongNameBuilder { + buf: Vec, + chksum: u8, + index: u8, +} + +#[cfg(feature = "alloc")] +impl LongNameBuilder { + fn new() -> Self { + LongNameBuilder { buf: Vec::::new(), chksum: 0, index: 0 } + } + + fn clear(&mut self) { + self.buf.clear(); + self.index = 0; + } + + fn into_vec(mut self) -> Vec { + // Check if last processed entry had index 1 + if self.index == 1 { + self.truncate(); + } else if !self.is_empty() { + warn!("unfinished LFN sequence {}", self.index); + self.clear(); + } + self.buf + } + + fn truncate(&mut self) { + // Truncate 0 and 0xFFFF characters from LFN buffer + let mut lfn_len = self.buf.len(); + while lfn_len > 0 { + match self.buf[lfn_len - 1] { + 0xFFFF | 0 => lfn_len -= 1, + _ => break, + } + } + self.buf.truncate(lfn_len); + } + + fn is_empty(&self) -> bool { + // Check if any LFN entry has been processed + // Note: index 0 is not a valid index in LFN and can be seen only after struct initialization + self.index == 0 + } + + fn process(&mut self, data: &DirLfnEntryData) { + let is_last = (data.order() & LFN_ENTRY_LAST_FLAG) != 0; + let index = data.order() & 0x1F; + if index == 0 { + // Corrupted entry + warn!("currupted lfn entry! {:x}", data.order()); + self.clear(); + return; + } + if is_last { + // last entry is actually first entry in stream + self.index = index; + self.chksum = data.checksum(); + self.buf.resize(index as usize * LFN_PART_LEN, 0); + } else if self.index == 0 || index != self.index - 1 || data.checksum() != self.chksum { + // Corrupted entry + warn!("currupted lfn entry! {:x} {:x} {:x} {:x}", data.order(), self.index, data.checksum(), self.chksum); + self.clear(); + return; + } else { + // Decrement LFN index only for non-last entries + self.index -= 1; + } + let pos = LFN_PART_LEN * (index - 1) as usize; + // copy name parts into LFN buffer + data.copy_name_to_slice(&mut self.buf[pos..pos + 13]); + } + + fn validate_chksum(&mut self, short_name: &[u8; 11]) { + if self.is_empty() { + // Nothing to validate - no LFN entries has been processed + return; + } + let chksum = lfn_checksum(short_name); + if chksum != self.chksum { + warn!("checksum mismatch {:x} {:x} {:?}", chksum, self.chksum, short_name); + self.clear(); + } + } +} + +// Dummy implementation for non-alloc build +#[cfg(not(feature = "alloc"))] +struct LongNameBuilder {} +#[cfg(not(feature = "alloc"))] +impl LongNameBuilder { + fn new() -> Self { + LongNameBuilder {} + } + fn clear(&mut self) {} + fn into_vec(self) {} + fn truncate(&mut self) {} + fn process(&mut self, _data: &DirLfnEntryData) {} + fn validate_chksum(&mut self, _short_name: &[u8; 11]) {} +} + +#[cfg(feature = "alloc")] +struct LfnEntriesGenerator<'a> { + name_parts_iter: iter::Rev>, + checksum: u8, + index: usize, + num: usize, + ended: bool, +} + +#[cfg(feature = "alloc")] +impl<'a> LfnEntriesGenerator<'a> { + fn new(name_utf16: &'a [u16], checksum: u8) -> Self { + let num_entries = (name_utf16.len() + LFN_PART_LEN - 1) / LFN_PART_LEN; + // create generator using reverse iterator over chunks - first chunk can be shorter + LfnEntriesGenerator { + checksum, + name_parts_iter: name_utf16.chunks(LFN_PART_LEN).rev(), + index: 0, + num: num_entries, + ended: false, + } + } +} + +#[cfg(feature = "alloc")] +impl<'a> Iterator for LfnEntriesGenerator<'a> { + type Item = DirLfnEntryData; + + fn next(&mut self) -> Option { + if self.ended { + return None; + } + + // get next part from reverse iterator + match self.name_parts_iter.next() { + Some(ref name_part) => { + let lfn_index = self.num - self.index; + let mut order = lfn_index as u8; + if self.index == 0 { + // this is last name part (written as first) + order |= LFN_ENTRY_LAST_FLAG; + } + debug_assert!(order > 0); + // name is padded with ' ' + let mut lfn_part = [0xFFFFu16; LFN_PART_LEN]; + lfn_part[..name_part.len()].copy_from_slice(&name_part); + if name_part.len() < LFN_PART_LEN { + // name is only zero-terminated if its length is not multiplicity of LFN_PART_LEN + lfn_part[name_part.len()] = 0; + } + // create and return new LFN entry + let mut lfn_entry = DirLfnEntryData::new(order, self.checksum); + lfn_entry.copy_name_from_slice(&lfn_part); + self.index += 1; + Some(lfn_entry) + }, + None => { + // end of name + self.ended = true; + None + }, + } + } + + fn size_hint(&self) -> (usize, Option) { + self.name_parts_iter.size_hint() + } +} + +// name_parts_iter is ExactSizeIterator so size_hint returns one limit +#[cfg(feature = "alloc")] +impl<'a> ExactSizeIterator for LfnEntriesGenerator<'a> {} + +// Dummy implementation for non-alloc build +#[cfg(not(feature = "alloc"))] +struct LfnEntriesGenerator {} +#[cfg(not(feature = "alloc"))] +impl LfnEntriesGenerator { + fn new(_name_utf16: &(), _checksum: u8) -> Self { + LfnEntriesGenerator {} + } +} +#[cfg(not(feature = "alloc"))] +impl Iterator for LfnEntriesGenerator { + type Item = DirLfnEntryData; + + fn next(&mut self) -> Option { + None + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(0)) + } +} +#[cfg(not(feature = "alloc"))] +impl ExactSizeIterator for LfnEntriesGenerator {} + +#[derive(Default, Debug, Clone)] +struct ShortNameGenerator { + chksum: u16, + long_prefix_bitmap: u16, + prefix_chksum_bitmap: u16, + name_fits: bool, + lossy_conv: bool, + exact_match: bool, + basename_len: u8, + short_name: [u8; 11], +} + +impl ShortNameGenerator { + fn new(name: &str) -> Self { + // padded by ' ' + let mut short_name = [SFN_PADDING; 11]; + // find extension after last dot + let (basename_len, name_fits, lossy_conv) = match name.rfind('.') { + Some(index) => { + // extension found - copy parts before and after dot + let (basename_len, basename_fits, basename_lossy) = + Self::copy_short_name_part(&mut short_name[0..8], &name[..index]); + let (_, ext_fits, ext_lossy) = Self::copy_short_name_part(&mut short_name[8..11], &name[index + 1..]); + (basename_len, basename_fits && ext_fits, basename_lossy || ext_lossy) + }, + None => { + // no extension - copy name and leave extension empty + let (basename_len, basename_fits, basename_lossy) = + Self::copy_short_name_part(&mut short_name[0..8], &name); + (basename_len, basename_fits, basename_lossy) + }, + }; + let chksum = Self::checksum(name); + Self { short_name, chksum, name_fits, lossy_conv, basename_len: basename_len as u8, ..Default::default() } + } + + fn generate_dot() -> [u8; 11] { + let mut short_name = [SFN_PADDING; 11]; + short_name[0] = 0x2e; + short_name + } + + fn generate_dotdot() -> [u8; 11] { + let mut short_name = [SFN_PADDING; 11]; + short_name[0] = 0x2e; + short_name[1] = 0x2e; + short_name + } + + fn copy_short_name_part(dst: &mut [u8], src: &str) -> (usize, bool, bool) { + let mut dst_pos = 0; + let mut lossy_conv = false; + for c in src.chars() { + if dst_pos == dst.len() { + // result buffer is full + return (dst_pos, false, lossy_conv); + } + // Make sure character is allowed in 8.3 name + let fixed_c = match c { + // strip spaces and dots + ' ' | '.' => { + lossy_conv = true; + continue; + }, + // copy allowed characters + 'A'...'Z' | 'a'...'z' | '0'...'9' => c, + '!' | '#' | '$' | '%' | '&' | '\'' | '(' | ')' | '-' | '@' | '^' | '_' | '`' | '{' | '}' | '~' => c, + // replace disallowed characters by underscore + _ => '_', + }; + // Update 'lossy conversion' flag + lossy_conv = lossy_conv || (fixed_c != c); + // short name is always uppercase + let upper = fixed_c.to_ascii_uppercase(); + dst[dst_pos] = upper as u8; // SAFE: upper is in range 0x20-0x7F + dst_pos += 1; + } + (dst_pos, true, lossy_conv) + } + + fn add_existing(&mut self, short_name: &[u8; 11]) { + // check for exact match collision + if short_name == &self.short_name { + self.exact_match = true; + } + // check for long prefix form collision (TEXTFI~1.TXT) + let prefix_len = cmp::min(self.basename_len, 6) as usize; + let num_suffix = + if short_name[prefix_len] == b'~' { (short_name[prefix_len + 1] as char).to_digit(10) } else { None }; + let ext_matches = short_name[8..] == self.short_name[8..]; + if short_name[..prefix_len] == self.short_name[..prefix_len] && num_suffix.is_some() && ext_matches { + let num = num_suffix.unwrap(); // SAFE + self.long_prefix_bitmap |= 1 << num; + } + + // check for short prefix + checksum form collision (TE021F~1.TXT) + let prefix_len = cmp::min(self.basename_len, 2) as usize; + let num_suffix = if short_name[prefix_len + 4] == b'~' { + (short_name[prefix_len + 4 + 1] as char).to_digit(10) + } else { + None + }; + if short_name[..prefix_len] == self.short_name[..prefix_len] && num_suffix.is_some() && ext_matches { + let chksum_res = + str::from_utf8(&short_name[prefix_len..prefix_len + 4]).map(|s| u16::from_str_radix(s, 16)); + if chksum_res == Ok(Ok(self.chksum)) { + let num = num_suffix.unwrap(); // SAFE + self.prefix_chksum_bitmap |= 1 << num; + } + } + } + + fn checksum(name: &str) -> u16 { + // BSD checksum algorithm + let mut chksum = num::Wrapping(0u16); + for c in name.chars() { + chksum = (chksum >> 1) + (chksum << 15) + num::Wrapping(c as u16); + } + chksum.0 + } + + fn generate(&self) -> io::Result<[u8; 11]> { + if !self.lossy_conv && self.name_fits && !self.exact_match { + // If there was no lossy conversion and name fits into + // 8.3 convention and there is no collision return it as is + return Ok(self.short_name); + } + // Try using long 6-characters prefix + for i in 1..5 { + if self.long_prefix_bitmap & (1 << i) == 0 { + return Ok(self.build_prefixed_name(i, false)); + } + } + // Try prefix with checksum + for i in 1..10 { + if self.prefix_chksum_bitmap & (1 << i) == 0 { + return Ok(self.build_prefixed_name(i, true)); + } + } + // Too many collisions - fail + Err(io::Error::new(ErrorKind::AlreadyExists, "short name already exists")) + } + + fn next_iteration(&mut self) { + // Try different checksum in next iteration + self.chksum = (num::Wrapping(self.chksum) + num::Wrapping(1)).0; + // Zero bitmaps + self.long_prefix_bitmap = 0; + self.prefix_chksum_bitmap = 0; + } + + fn build_prefixed_name(&self, num: u32, with_chksum: bool) -> [u8; 11] { + let mut buf = [SFN_PADDING; 11]; + let prefix_len = if with_chksum { + let prefix_len = cmp::min(self.basename_len as usize, 2); + buf[..prefix_len].copy_from_slice(&self.short_name[..prefix_len]); + buf[prefix_len..prefix_len + 4].copy_from_slice(&Self::u16_to_u8_array(self.chksum)); + prefix_len + 4 + } else { + let prefix_len = cmp::min(self.basename_len as usize, 6); + buf[..prefix_len].copy_from_slice(&self.short_name[..prefix_len]); + prefix_len + }; + buf[prefix_len] = b'~'; + buf[prefix_len + 1] = char::from_digit(num, 10).unwrap() as u8; // SAFE + buf[8..].copy_from_slice(&self.short_name[8..]); + buf + } + + fn u16_to_u8_array(x: u16) -> [u8; 4] { + let c1 = char::from_digit((x as u32 >> 12) & 0xF, 16).unwrap().to_ascii_uppercase() as u8; + let c2 = char::from_digit((x as u32 >> 8) & 0xF, 16).unwrap().to_ascii_uppercase() as u8; + let c3 = char::from_digit((x as u32 >> 4) & 0xF, 16).unwrap().to_ascii_uppercase() as u8; + let c4 = char::from_digit((x as u32 >> 0) & 0xF, 16).unwrap().to_ascii_uppercase() as u8; + [c1, c2, c3, c4] + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_split_path() { + assert_eq!(split_path("aaa/bbb/ccc"), ("aaa", Some("bbb/ccc"))); + assert_eq!(split_path("aaa/bbb"), ("aaa", Some("bbb"))); + assert_eq!(split_path("aaa"), ("aaa", None)); + } + + #[test] + fn test_generate_short_name() { + assert_eq!(&ShortNameGenerator::new("Foo").generate().unwrap(), b"FOO "); + assert_eq!(&ShortNameGenerator::new("Foo.b").generate().unwrap(), b"FOO B "); + assert_eq!(&ShortNameGenerator::new("Foo.baR").generate().unwrap(), b"FOO BAR"); + assert_eq!(&ShortNameGenerator::new("Foo+1.baR").generate().unwrap(), b"FOO_1~1 BAR"); + assert_eq!(&ShortNameGenerator::new("ver +1.2.text").generate().unwrap(), b"VER_12~1TEX"); + assert_eq!(&ShortNameGenerator::new(".bashrc.swp").generate().unwrap(), b"BASHRC~1SWP"); + } + + #[test] + fn test_short_name_checksum_overflow() { + ShortNameGenerator::checksum("\u{FF5A}\u{FF5A}\u{FF5A}\u{FF5A}"); + } + + #[test] + fn test_lfn_checksum_overflow() { + lfn_checksum(&[0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8, 0xFFu8]); + } + + #[test] + fn test_generate_short_name_collisions_long() { + let mut buf: [u8; 11]; + let mut gen = ShortNameGenerator::new("TextFile.Mine.txt"); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TEXTFI~1TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TEXTFI~2TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TEXTFI~3TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TEXTFI~4TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TE527D~1TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TE527D~2TXT"); + for i in 3..10 { + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, format!("TE527D~{}TXT", i).as_bytes()); + } + gen.add_existing(&buf); + assert!(gen.generate().is_err()); + gen.next_iteration(); + for _i in 0..4 { + buf = gen.generate().unwrap(); + gen.add_existing(&buf); + } + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"TE527E~1TXT"); + } + + #[test] + fn test_generate_short_name_collisions_short() { + let mut buf: [u8; 11]; + let mut gen = ShortNameGenerator::new("x.txt"); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X~1 TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X~2 TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X~3 TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X~4 TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X40DA~1 TXT"); + gen.add_existing(&buf); + buf = gen.generate().unwrap(); + assert_eq!(&buf, b"X40DA~2 TXT"); + } +} diff --git a/src/dir_entry.rs b/src/dir_entry.rs new file mode 100644 index 0000000..ba4ac5e --- /dev/null +++ b/src/dir_entry.rs @@ -0,0 +1,687 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::{string::String, vec::Vec}; +use core::char; +use core::iter::FromIterator; +use core::{fmt, str}; +use io; +use io::prelude::*; +use io::Cursor; + +use byteorder::LittleEndian; +use byteorder_ext::{ReadBytesExt, WriteBytesExt}; + +use dir::{Dir, DirRawStream}; +use file::File; +use fs::{FatType, FileSystem, OemCpConverter, ReadWriteSeek}; +use time::{Date, DateTime}; + +bitflags! { + /// A FAT file attributes. + #[derive(Default)] + pub struct FileAttributes: u8 { + const READ_ONLY = 0x01; + const HIDDEN = 0x02; + const SYSTEM = 0x04; + const VOLUME_ID = 0x08; + const DIRECTORY = 0x10; + const ARCHIVE = 0x20; + const LFN = Self::READ_ONLY.bits | Self::HIDDEN.bits + | Self::SYSTEM.bits | Self::VOLUME_ID.bits; + } +} + +// Size of single directory entry in bytes +pub(crate) const DIR_ENTRY_SIZE: u64 = 32; + +// Directory entry flags available in first byte of the short name +pub(crate) const DIR_ENTRY_DELETED_FLAG: u8 = 0xE5; +pub(crate) const DIR_ENTRY_REALLY_E5_FLAG: u8 = 0x05; + +// Length in characters of a LFN fragment packed in one directory entry +pub(crate) const LFN_PART_LEN: usize = 13; + +// Bit used in order field to mark last LFN entry +pub(crate) const LFN_ENTRY_LAST_FLAG: u8 = 0x40; + +/// Decoded file short name +#[derive(Clone, Debug, Default)] +pub(crate) struct ShortName { + name: [u8; 12], + len: u8, +} + +impl ShortName { + const PADDING: u8 = b' '; + + pub(crate) fn new(raw_name: &[u8; 11]) -> Self { + // get name components length by looking for space character + let name_len = raw_name[0..8].iter().rposition(|x| *x != Self::PADDING).map(|p| p + 1).unwrap_or(0); + let ext_len = raw_name[8..11].iter().rposition(|x| *x != Self::PADDING).map(|p| p + 1).unwrap_or(0); + let mut name = [Self::PADDING; 12]; + name[..name_len].copy_from_slice(&raw_name[..name_len]); + let total_len = if ext_len > 0 { + name[name_len] = b'.'; + name[name_len + 1..name_len + 1 + ext_len].copy_from_slice(&raw_name[8..8 + ext_len]); + // Return total name length + name_len + 1 + ext_len + } else { + // No extension - return length of name part + name_len + }; + // FAT encodes character 0xE5 as 0x05 because 0xE5 marks deleted files + if name[0] == DIR_ENTRY_REALLY_E5_FLAG { + name[0] = 0xE5; + } + // Short names in FAT filesystem are encoded in OEM code-page + ShortName { name, len: total_len as u8 } + } + + fn as_bytes(&self) -> &[u8] { + &self.name[..self.len as usize] + } + + #[cfg(feature = "alloc")] + fn to_string(&self, oem_cp_converter: &OemCpConverter) -> String { + // Strip non-ascii characters from short name + let char_iter = self.as_bytes().iter().cloned().map(|c| oem_cp_converter.decode(c)); + // Build string from character iterator + String::from_iter(char_iter) + } + + fn eq_ignore_case(&self, name: &str, oem_cp_converter: &OemCpConverter) -> bool { + // Strip non-ascii characters from short name + let byte_iter = self.as_bytes().iter().cloned(); + let char_iter = byte_iter.map(|c| oem_cp_converter.decode(c)); + let uppercase_char_iter = char_iter.flat_map(|c| c.to_uppercase()); + // Build string from character iterator + uppercase_char_iter.eq(name.chars().flat_map(|c| c.to_uppercase())) + } +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Default)] +pub(crate) struct DirFileEntryData { + name: [u8; 11], + attrs: FileAttributes, + reserved_0: u8, + create_time_0: u8, + create_time_1: u16, + create_date: u16, + access_date: u16, + first_cluster_hi: u16, + modify_time: u16, + modify_date: u16, + first_cluster_lo: u16, + size: u32, +} + +impl DirFileEntryData { + pub(crate) fn new(name: [u8; 11], attrs: FileAttributes) -> Self { + DirFileEntryData { name, attrs, ..Default::default() } + } + + pub(crate) fn renamed(&self, new_name: [u8; 11]) -> Self { + let mut sfn_entry = self.clone(); + sfn_entry.name = new_name; + sfn_entry + } + + pub(crate) fn name(&self) -> &[u8; 11] { + &self.name + } + + #[cfg(feature = "alloc")] + fn lowercase_name(&self) -> ShortName { + let mut name_copy: [u8; 11] = self.name; + if self.lowercase_basename() { + for c in &mut name_copy[..8] { + *c = (*c as char).to_ascii_lowercase() as u8; + } + } + if self.lowercase_ext() { + for c in &mut name_copy[8..] { + *c = (*c as char).to_ascii_lowercase() as u8; + } + } + ShortName::new(&name_copy) + } + + pub(crate) fn first_cluster(&self, fat_type: FatType) -> Option { + let first_cluster_hi = if fat_type == FatType::Fat32 { self.first_cluster_hi } else { 0 }; + let n = ((first_cluster_hi as u32) << 16) | self.first_cluster_lo as u32; + if n == 0 { + None + } else { + Some(n) + } + } + + pub(crate) fn set_first_cluster(&mut self, cluster: Option, fat_type: FatType) { + let n = cluster.unwrap_or(0); + if fat_type == FatType::Fat32 { + self.first_cluster_hi = (n >> 16) as u16; + } + self.first_cluster_lo = (n & 0xFFFF) as u16; + } + + pub(crate) fn size(&self) -> Option { + if self.is_file() { + Some(self.size) + } else { + None + } + } + + fn set_size(&mut self, size: u32) { + self.size = size; + } + + pub(crate) fn is_dir(&self) -> bool { + self.attrs.contains(FileAttributes::DIRECTORY) + } + + fn is_file(&self) -> bool { + !self.is_dir() + } + + fn lowercase_basename(&self) -> bool { + self.reserved_0 & (1 << 3) != 0 + } + + fn lowercase_ext(&self) -> bool { + self.reserved_0 & (1 << 4) != 0 + } + + fn created(&self) -> DateTime { + DateTime::decode(self.create_date, self.create_time_1, self.create_time_0) + } + + fn accessed(&self) -> Date { + Date::decode(self.access_date) + } + + fn modified(&self) -> DateTime { + DateTime::decode(self.modify_date, self.modify_time, 0) + } + + pub(crate) fn set_created(&mut self, date_time: DateTime) { + self.create_date = date_time.date.encode(); + let encoded_time = date_time.time.encode(); + self.create_time_1 = encoded_time.0; + self.create_time_0 = encoded_time.1; + } + + pub(crate) fn set_accessed(&mut self, date: Date) { + self.access_date = date.encode(); + } + + pub(crate) fn set_modified(&mut self, date_time: DateTime) { + self.modify_date = date_time.date.encode(); + self.modify_time = date_time.time.encode().0; + } + + pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> { + wrt.write_all(&self.name)?; + wrt.write_u8(self.attrs.bits())?; + wrt.write_u8(self.reserved_0)?; + wrt.write_u8(self.create_time_0)?; + wrt.write_u16::(self.create_time_1)?; + wrt.write_u16::(self.create_date)?; + wrt.write_u16::(self.access_date)?; + wrt.write_u16::(self.first_cluster_hi)?; + wrt.write_u16::(self.modify_time)?; + wrt.write_u16::(self.modify_date)?; + wrt.write_u16::(self.first_cluster_lo)?; + wrt.write_u32::(self.size)?; + Ok(()) + } + + pub(crate) fn is_deleted(&self) -> bool { + self.name[0] == DIR_ENTRY_DELETED_FLAG + } + + pub(crate) fn set_deleted(&mut self) { + self.name[0] = DIR_ENTRY_DELETED_FLAG; + } + + pub(crate) fn is_end(&self) -> bool { + self.name[0] == 0 + } + + pub(crate) fn is_volume(&self) -> bool { + self.attrs.contains(FileAttributes::VOLUME_ID) + } +} + +#[allow(dead_code)] +#[derive(Clone, Debug, Default)] +pub(crate) struct DirLfnEntryData { + order: u8, + name_0: [u16; 5], + attrs: FileAttributes, + entry_type: u8, + checksum: u8, + name_1: [u16; 6], + reserved_0: u16, + name_2: [u16; 2], +} + +impl DirLfnEntryData { + pub(crate) fn new(order: u8, checksum: u8) -> Self { + DirLfnEntryData { order, checksum, attrs: FileAttributes::LFN, ..Default::default() } + } + + pub(crate) fn copy_name_from_slice(&mut self, lfn_part: &[u16; LFN_PART_LEN]) { + self.name_0.copy_from_slice(&lfn_part[0..5]); + self.name_1.copy_from_slice(&lfn_part[5..5 + 6]); + self.name_2.copy_from_slice(&lfn_part[11..11 + 2]); + } + + pub(crate) fn copy_name_to_slice(&self, lfn_part: &mut [u16]) { + debug_assert!(lfn_part.len() == LFN_PART_LEN); + lfn_part[0..5].copy_from_slice(&self.name_0); + lfn_part[5..11].copy_from_slice(&self.name_1); + lfn_part[11..13].copy_from_slice(&self.name_2); + } + + pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> { + wrt.write_u8(self.order)?; + for ch in self.name_0.iter() { + wrt.write_u16::(*ch)?; + } + wrt.write_u8(self.attrs.bits())?; + wrt.write_u8(self.entry_type)?; + wrt.write_u8(self.checksum)?; + for ch in self.name_1.iter() { + wrt.write_u16::(*ch)?; + } + wrt.write_u16::(self.reserved_0)?; + for ch in self.name_2.iter() { + wrt.write_u16::(*ch)?; + } + Ok(()) + } + + #[cfg(feature = "alloc")] + pub(crate) fn order(&self) -> u8 { + self.order + } + + #[cfg(feature = "alloc")] + pub(crate) fn checksum(&self) -> u8 { + self.checksum + } + + pub(crate) fn is_deleted(&self) -> bool { + self.order == DIR_ENTRY_DELETED_FLAG + } + + pub(crate) fn set_deleted(&mut self) { + self.order = DIR_ENTRY_DELETED_FLAG; + } + + pub(crate) fn is_end(&self) -> bool { + self.order == 0 + } +} + +#[derive(Clone, Debug)] +pub(crate) enum DirEntryData { + File(DirFileEntryData), + Lfn(DirLfnEntryData), +} + +impl DirEntryData { + pub(crate) fn serialize(&self, wrt: &mut Write) -> io::Result<()> { + match self { + &DirEntryData::File(ref file) => file.serialize(wrt), + &DirEntryData::Lfn(ref lfn) => lfn.serialize(wrt), + } + } + + pub(crate) fn deserialize(rdr: &mut Read) -> io::Result { + let mut name = [0; 11]; + match rdr.read_exact(&mut name) { + Err(ref err) if err.kind() == io::ErrorKind::UnexpectedEof => { + // entries can occupy all clusters of directory so there is no zero entry at the end + // handle it here by returning non-existing empty entry + return Ok(DirEntryData::File(DirFileEntryData { ..Default::default() })); + }, + Err(err) => return Err(err), + _ => {}, + } + let attrs = FileAttributes::from_bits_truncate(rdr.read_u8()?); + if attrs & FileAttributes::LFN == FileAttributes::LFN { + // read long name entry + let mut data = DirLfnEntryData { attrs, ..Default::default() }; + // use cursor to divide name into order and LFN name_0 + let mut cur = Cursor::new(&name); + data.order = cur.read_u8()?; + cur.read_u16_into::(&mut data.name_0)?; + data.entry_type = rdr.read_u8()?; + data.checksum = rdr.read_u8()?; + rdr.read_u16_into::(&mut data.name_1)?; + data.reserved_0 = rdr.read_u16::()?; + rdr.read_u16_into::(&mut data.name_2)?; + Ok(DirEntryData::Lfn(data)) + } else { + // read short name entry + let data = DirFileEntryData { + name, + attrs, + reserved_0: rdr.read_u8()?, + create_time_0: rdr.read_u8()?, + create_time_1: rdr.read_u16::()?, + create_date: rdr.read_u16::()?, + access_date: rdr.read_u16::()?, + first_cluster_hi: rdr.read_u16::()?, + modify_time: rdr.read_u16::()?, + modify_date: rdr.read_u16::()?, + first_cluster_lo: rdr.read_u16::()?, + size: rdr.read_u32::()?, + }; + Ok(DirEntryData::File(data)) + } + } + + pub(crate) fn is_deleted(&self) -> bool { + match self { + &DirEntryData::File(ref file) => file.is_deleted(), + &DirEntryData::Lfn(ref lfn) => lfn.is_deleted(), + } + } + + pub(crate) fn set_deleted(&mut self) { + match self { + &mut DirEntryData::File(ref mut file) => file.set_deleted(), + &mut DirEntryData::Lfn(ref mut lfn) => lfn.set_deleted(), + } + } + + pub(crate) fn is_end(&self) -> bool { + match self { + &DirEntryData::File(ref file) => file.is_end(), + &DirEntryData::Lfn(ref lfn) => lfn.is_end(), + } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct DirEntryEditor { + data: DirFileEntryData, + pos: u64, + dirty: bool, +} + +impl DirEntryEditor { + fn new(data: DirFileEntryData, pos: u64) -> Self { + DirEntryEditor { data, pos, dirty: false } + } + + pub(crate) fn inner(&self) -> &DirFileEntryData { + &self.data + } + + pub(crate) fn set_first_cluster(&mut self, first_cluster: Option, fat_type: FatType) { + if first_cluster != self.data.first_cluster(fat_type) { + self.data.set_first_cluster(first_cluster, fat_type); + self.dirty = true; + } + } + + pub(crate) fn set_size(&mut self, size: u32) { + match self.data.size() { + Some(n) if size != n => { + self.data.set_size(size); + self.dirty = true; + }, + _ => {}, + } + } + + pub(crate) fn set_created(&mut self, date_time: DateTime) { + if date_time != self.data.created() { + self.data.set_created(date_time); + self.dirty = true; + } + } + + pub(crate) fn set_accessed(&mut self, date: Date) { + if date != self.data.accessed() { + self.data.set_accessed(date); + self.dirty = true; + } + } + + pub(crate) fn set_modified(&mut self, date_time: DateTime) { + if date_time != self.data.modified() { + self.data.set_modified(date_time); + self.dirty = true; + } + } + + pub(crate) fn flush(&mut self, fs: &FileSystem) -> io::Result<()> { + if self.dirty { + self.write(fs)?; + self.dirty = false; + } + Ok(()) + } + + fn write(&self, fs: &FileSystem) -> io::Result<()> { + let mut disk = fs.disk.borrow_mut(); + disk.seek(io::SeekFrom::Start(self.pos))?; + self.data.serialize(&mut *disk) + } +} + +/// A FAT directory entry. +/// +/// `DirEntry` is returned by `DirIter` when reading a directory. +#[derive(Clone)] +pub struct DirEntry<'a, T: ReadWriteSeek + 'a> { + pub(crate) data: DirFileEntryData, + pub(crate) short_name: ShortName, + #[cfg(feature = "alloc")] + pub(crate) lfn_utf16: Vec, + #[cfg(not(feature = "alloc"))] + pub(crate) lfn_utf16: (), + pub(crate) entry_pos: u64, + pub(crate) offset_range: (u64, u64), + pub(crate) fs: &'a FileSystem, +} + +impl<'a, T: ReadWriteSeek> DirEntry<'a, T> { + /// Returns short file name. + /// + /// Non-ASCII characters are replaced by the replacement character (U+FFFD). + #[cfg(feature = "alloc")] + pub fn short_file_name(&self) -> String { + self.short_name.to_string(self.fs.options.oem_cp_converter) + } + + /// Returns short file name as byte array slice. + /// + /// Characters are encoded in the OEM codepage. + pub fn short_file_name_as_bytes(&self) -> &[u8] { + self.short_name.as_bytes() + } + + /// Returns long file name or if it doesn't exist fallbacks to short file name. + #[cfg(feature = "alloc")] + pub fn file_name(&self) -> String { + if self.lfn_utf16.is_empty() { + self.data.lowercase_name().to_string(self.fs.options.oem_cp_converter) + } else { + String::from_utf16_lossy(&self.lfn_utf16) + } + } + + /// Returns file attributes. + pub fn attributes(&self) -> FileAttributes { + self.data.attrs + } + + /// Checks if entry belongs to directory. + pub fn is_dir(&self) -> bool { + self.data.is_dir() + } + + /// Checks if entry belongs to regular file. + pub fn is_file(&self) -> bool { + self.data.is_file() + } + + pub(crate) fn first_cluster(&self) -> Option { + self.data.first_cluster(self.fs.fat_type()) + } + + fn editor(&self) -> DirEntryEditor { + DirEntryEditor::new(self.data.clone(), self.entry_pos) + } + + pub(crate) fn is_same_entry(&self, other: &DirEntry) -> bool { + self.entry_pos == other.entry_pos + } + + /// Returns `File` struct for this entry. + /// + /// Panics if this is not a file. + pub fn to_file(&self) -> File<'a, T> { + assert!(!self.is_dir(), "Not a file entry"); + File::new(self.first_cluster(), Some(self.editor()), self.fs) + } + + /// Returns `Dir` struct for this entry. + /// + /// Panics if this is not a directory. + pub fn to_dir(&self) -> Dir<'a, T> { + assert!(self.is_dir(), "Not a directory entry"); + match self.first_cluster() { + Some(n) => { + let file = File::new(Some(n), Some(self.editor()), self.fs); + Dir::new(DirRawStream::File(file), self.fs) + }, + None => self.fs.root_dir(), + } + } + + /// Returns file size or 0 for directory. + pub fn len(&self) -> u64 { + self.data.size as u64 + } + + /// Returns file creation date and time. + /// + /// Resolution of the time field is 1/100s. + pub fn created(&self) -> DateTime { + self.data.created() + } + + /// Returns file last access date. + pub fn accessed(&self) -> Date { + self.data.accessed() + } + + /// Returns file last modification date and time. + /// + /// Resolution of the time field is 2s. + pub fn modified(&self) -> DateTime { + self.data.modified() + } + + pub(crate) fn raw_short_name(&self) -> &[u8; 11] { + &self.data.name + } + + #[cfg(feature = "alloc")] + pub(crate) fn eq_name(&self, name: &str) -> bool { + let self_name = self.file_name(); + let self_name_lowercase_iter = self_name.chars().flat_map(|c| c.to_uppercase()); + let other_name_lowercase_iter = name.chars().flat_map(|c| c.to_uppercase()); + let long_name_matches = self_name_lowercase_iter.eq(other_name_lowercase_iter); + let short_name_matches = self.short_name.eq_ignore_case(name, self.fs.options.oem_cp_converter); + long_name_matches || short_name_matches + } + #[cfg(not(feature = "alloc"))] + pub(crate) fn eq_name(&self, name: &str) -> bool { + self.short_name.eq_ignore_case(name, self.fs.options.oem_cp_converter) + } +} + +impl<'a, T: ReadWriteSeek> fmt::Debug for DirEntry<'a, T> { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + self.data.fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use fs::LOSSY_OEM_CP_CONVERTER; + + #[test] + fn short_name_with_ext() { + let mut raw_short_name = [0u8; 11]; + raw_short_name.copy_from_slice(b"FOO BAR"); + assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.BAR"); + raw_short_name.copy_from_slice(b"LOOK AT M E"); + assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "LOOK AT.M E"); + raw_short_name[0] = 0x99; + raw_short_name[10] = 0x99; + assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "\u{FFFD}OOK AT.M \u{FFFD}"); + assert_eq!( + ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &LOSSY_OEM_CP_CONVERTER), + true + ); + } + + #[test] + fn short_name_without_ext() { + let mut raw_short_name = [0u8; 11]; + raw_short_name.copy_from_slice(b"FOO "); + assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "FOO"); + raw_short_name.copy_from_slice(b"LOOK AT "); + assert_eq!(ShortName::new(&raw_short_name).to_string(&LOSSY_OEM_CP_CONVERTER), "LOOK AT"); + } + + #[test] + fn short_name_eq_ignore_case() { + let mut raw_short_name = [0u8; 11]; + raw_short_name.copy_from_slice(b"LOOK AT M E"); + raw_short_name[0] = 0x99; + raw_short_name[10] = 0x99; + assert_eq!( + ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}OOK AT.M \u{FFFD}", &LOSSY_OEM_CP_CONVERTER), + true + ); + assert_eq!( + ShortName::new(&raw_short_name).eq_ignore_case("\u{FFFD}ook AT.m \u{FFFD}", &LOSSY_OEM_CP_CONVERTER), + true + ); + } + + #[test] + fn short_name_05_changed_to_e5() { + let raw_short_name = [0x05; 11]; + assert_eq!( + ShortName::new(&raw_short_name).as_bytes(), + [0xE5, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, 0x05, b'.', 0x05, 0x05, 0x05] + ); + } + + #[test] + fn lowercase_short_name() { + let mut raw_short_name = [0u8; 11]; + raw_short_name.copy_from_slice(b"FOO RS "); + let mut raw_entry = + DirFileEntryData { name: raw_short_name, reserved_0: (1 << 3) | (1 << 4), ..Default::default() }; + assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "foo.rs"); + raw_entry.reserved_0 = 1 << 3; + assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "foo.RS"); + raw_entry.reserved_0 = 1 << 4; + assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.rs"); + raw_entry.reserved_0 = 0; + assert_eq!(raw_entry.lowercase_name().to_string(&LOSSY_OEM_CP_CONVERTER), "FOO.RS"); + } +} diff --git a/src/file.rs b/src/file.rs new file mode 100644 index 0000000..5b8051a --- /dev/null +++ b/src/file.rs @@ -0,0 +1,370 @@ +use core; +use core::cmp; +use io; +use io::prelude::*; +use io::{ErrorKind, SeekFrom}; + +use dir_entry::DirEntryEditor; +use fs::{FileSystem, ReadWriteSeek}; +use time::{Date, DateTime}; + +const MAX_FILE_SIZE: u32 = core::u32::MAX; + +/// A FAT filesystem file object used for reading and writing data. +/// +/// This struct is created by the `open_file` or `create_file` methods on `Dir`. +pub struct File<'a, T: ReadWriteSeek + 'a> { + // Note first_cluster is None if file is empty + first_cluster: Option, + // Note: if offset points between clusters current_cluster is the previous cluster + current_cluster: Option, + // current position in this file + offset: u32, + // file dir entry editor - None for root dir + entry: Option, + // file-system reference + fs: &'a FileSystem, +} + +impl<'a, T: ReadWriteSeek> File<'a, T> { + pub(crate) fn new(first_cluster: Option, entry: Option, fs: &'a FileSystem) -> Self { + File { + first_cluster, + entry, + fs, + current_cluster: None, // cluster before first one + offset: 0, + } + } + + fn update_dir_entry_after_write(&mut self) { + let offset = self.offset; + if let Some(ref mut e) = self.entry { + let now = self.fs.options.time_provider.get_current_date_time(); + e.set_modified(now); + if e.inner().size().map_or(false, |s| offset > s) { + e.set_size(offset); + } + } + } + + /// Truncate file in current position. + pub fn truncate(&mut self) -> io::Result<()> { + if let Some(ref mut e) = self.entry { + e.set_size(self.offset); + if self.offset == 0 { + e.set_first_cluster(None, self.fs.fat_type()); + } + } + if self.offset > 0 { + debug_assert!(self.current_cluster.is_some()); + // if offset is not 0 current cluster cannot be empty + self.fs.truncate_cluster_chain(self.current_cluster.unwrap()) // SAFE + } else { + debug_assert!(self.current_cluster.is_none()); + if let Some(n) = self.first_cluster { + self.fs.free_cluster_chain(n)?; + self.first_cluster = None; + } + Ok(()) + } + } + + pub(crate) fn abs_pos(&self) -> Option { + // Returns current position relative to filesystem start + // Note: when between clusters it returns position after previous cluster + match self.current_cluster { + Some(n) => { + let cluster_size = self.fs.cluster_size(); + let offset_mod_cluster_size = self.offset % cluster_size; + let offset_in_cluster = if offset_mod_cluster_size == 0 { + // position points between clusters - we are returning previous cluster so + // offset must be set to the cluster size + cluster_size + } else { + offset_mod_cluster_size + }; + let offset_in_fs = self.fs.offset_from_cluster(n) + u64::from(offset_in_cluster); + Some(offset_in_fs) + }, + None => None, + } + } + + fn flush_dir_entry(&mut self) -> io::Result<()> { + if let Some(ref mut e) = self.entry { + e.flush(self.fs)?; + } + Ok(()) + } + + /// Sets date and time of creation for this file. + /// + /// Note: it is set to a value from the `TimeProvider` when creating a file. + /// Deprecated: if needed implement a custom `TimeProvider`. + #[deprecated] + pub fn set_created(&mut self, date_time: DateTime) { + if let Some(ref mut e) = self.entry { + e.set_created(date_time); + } + } + + /// Sets date of last access for this file. + /// + /// Note: it is overwritten by a value from the `TimeProvider` on every file read operation. + /// Deprecated: if needed implement a custom `TimeProvider`. + #[deprecated] + pub fn set_accessed(&mut self, date: Date) { + if let Some(ref mut e) = self.entry { + e.set_accessed(date); + } + } + + /// Sets date and time of last modification for this file. + /// + /// Note: it is overwritten by a value from the `TimeProvider` on every file write operation. + /// Deprecated: if needed implement a custom `TimeProvider`. + #[deprecated] + pub fn set_modified(&mut self, date_time: DateTime) { + if let Some(ref mut e) = self.entry { + e.set_modified(date_time); + } + } + + fn size(&self) -> Option { + match self.entry { + Some(ref e) => e.inner().size(), + None => None, + } + } + + fn is_dir(&self) -> bool { + match self.entry { + Some(ref e) => e.inner().is_dir(), + None => false, + } + } + + fn bytes_left_in_file(&self) -> Option { + // Note: seeking beyond end of file is not allowed so overflow is impossible + self.size().map(|s| (s - self.offset) as usize) + } + + fn set_first_cluster(&mut self, cluster: u32) { + self.first_cluster = Some(cluster); + if let Some(ref mut e) = self.entry { + e.set_first_cluster(self.first_cluster, self.fs.fat_type()); + } + } + + pub(crate) fn first_cluster(&self) -> Option { + self.first_cluster + } +} + +impl<'a, T: ReadWriteSeek> Drop for File<'a, T> { + fn drop(&mut self) { + if let Err(err) = self.flush() { + error!("flush failed {}", err); + } + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl<'a, T: ReadWriteSeek> Clone for File<'a, T> { + fn clone(&self) -> Self { + File { + first_cluster: self.first_cluster, + current_cluster: self.current_cluster, + offset: self.offset, + entry: self.entry.clone(), + fs: self.fs, + } + } +} + +impl<'a, T: ReadWriteSeek> Read for File<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let cluster_size = self.fs.cluster_size(); + let current_cluster_opt = if self.offset % cluster_size == 0 { + // next cluster + match self.current_cluster { + None => self.first_cluster, + Some(n) => { + let r = self.fs.cluster_iter(n).next(); + match r { + Some(Err(err)) => return Err(err), + Some(Ok(n)) => Some(n), + None => None, + } + }, + } + } else { + self.current_cluster + }; + let current_cluster = match current_cluster_opt { + Some(n) => n, + None => return Ok(0), + }; + let offset_in_cluster = self.offset % cluster_size; + let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize; + let bytes_left_in_file = self.bytes_left_in_file().unwrap_or(bytes_left_in_cluster); + let read_size = cmp::min(cmp::min(buf.len(), bytes_left_in_cluster), bytes_left_in_file); + if read_size == 0 { + return Ok(0); + } + trace!("read {} bytes in cluster {}", read_size, current_cluster); + let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64); + let read_bytes = { + let mut disk = self.fs.disk.borrow_mut(); + disk.seek(SeekFrom::Start(offset_in_fs))?; + disk.read(&mut buf[..read_size])? + }; + if read_bytes == 0 { + return Ok(0); + } + self.offset += read_bytes as u32; + self.current_cluster = Some(current_cluster); + + if let Some(ref mut e) = self.entry { + if self.fs.options.update_accessed_date { + let now = self.fs.options.time_provider.get_current_date(); + e.set_accessed(now); + } + } + Ok(read_bytes) + } +} + +impl<'a, T: ReadWriteSeek> Write for File<'a, T> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let cluster_size = self.fs.cluster_size(); + let offset_in_cluster = self.offset % cluster_size; + let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize; + let bytes_left_until_max_file_size = (MAX_FILE_SIZE - self.offset) as usize; + let write_size = cmp::min(buf.len(), bytes_left_in_cluster); + let write_size = cmp::min(write_size, bytes_left_until_max_file_size); + // Exit early if we are going to write no data + if write_size == 0 { + return Ok(0); + } + // Mark the volume 'dirty' + self.fs.set_dirty_flag(true)?; + // Get cluster for write possibly allocating new one + let current_cluster = if self.offset % cluster_size == 0 { + // next cluster + let next_cluster = match self.current_cluster { + None => self.first_cluster, + Some(n) => { + let r = self.fs.cluster_iter(n).next(); + match r { + Some(Err(err)) => return Err(err), + Some(Ok(n)) => Some(n), + None => None, + } + }, + }; + match next_cluster { + Some(n) => n, + None => { + // end of chain reached - allocate new cluster + let new_cluster = self.fs.alloc_cluster(self.current_cluster, self.is_dir())?; + trace!("allocated cluser {}", new_cluster); + if self.first_cluster.is_none() { + self.set_first_cluster(new_cluster); + } + new_cluster + }, + } + } else { + // self.current_cluster should be a valid cluster + match self.current_cluster { + Some(n) => n, + None => panic!("Offset inside cluster but no cluster allocated"), + } + }; + trace!("write {} bytes in cluster {}", write_size, current_cluster); + let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64); + let written_bytes = { + let mut disk = self.fs.disk.borrow_mut(); + disk.seek(SeekFrom::Start(offset_in_fs))?; + disk.write(&buf[..write_size])? + }; + if written_bytes == 0 { + return Ok(0); + } + // some bytes were writter - update position and optionally size + self.offset += written_bytes as u32; + self.current_cluster = Some(current_cluster); + self.update_dir_entry_after_write(); + Ok(written_bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.flush_dir_entry()?; + let mut disk = self.fs.disk.borrow_mut(); + disk.flush() + } +} + +impl<'a, T: ReadWriteSeek> Seek for File<'a, T> { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let mut new_pos = match pos { + SeekFrom::Current(x) => self.offset as i64 + x, + SeekFrom::Start(x) => x as i64, + SeekFrom::End(x) => { + let size = self.size().expect("cannot seek from end if size is unknown") as i64; + size + x + }, + }; + if new_pos < 0 { + return Err(io::Error::new(ErrorKind::InvalidInput, "Seek to a negative offset")); + } + if let Some(s) = self.size() { + if new_pos > s as i64 { + info!("seek beyond end of file"); + new_pos = s as i64; + } + } + let mut new_pos = new_pos as u32; + trace!("file seek {} -> {} - entry {:?}", self.offset, new_pos, self.entry); + if new_pos == self.offset { + // position is the same - nothing to do + return Ok(self.offset as u64); + } + // get number of clusters to seek (favoring previous cluster in corner case) + let cluster_count = (self.fs.clusters_from_bytes(new_pos as u64) as i32 - 1) as isize; + let old_cluster_count = (self.fs.clusters_from_bytes(self.offset as u64) as i32 - 1) as isize; + let new_cluster = if new_pos == 0 { + None + } else if cluster_count == old_cluster_count { + self.current_cluster + } else { + match self.first_cluster { + Some(n) => { + let mut cluster = n; + let mut iter = self.fs.cluster_iter(n); + for i in 0..cluster_count { + cluster = match iter.next() { + Some(r) => r?, + None => { + // chain ends before new position - seek to end of last cluster + new_pos = self.fs.bytes_from_clusters((i + 1) as u32) as u32; + break; + }, + }; + } + Some(cluster) + }, + None => { + // empty file - always seek to 0 + new_pos = 0; + None + }, + } + }; + self.offset = new_pos as u32; + self.current_cluster = new_cluster; + Ok(self.offset as u64) + } +} diff --git a/src/fs.rs b/src/fs.rs new file mode 100644 index 0000000..a5ee63f --- /dev/null +++ b/src/fs.rs @@ -0,0 +1,1005 @@ +#[cfg(all(not(feature = "std"), feature = "alloc"))] +use alloc::string::String; +use core::cell::{Cell, RefCell}; +use core::char; +use core::cmp; +use core::fmt::Debug; +use core::iter::FromIterator; +use core::u32; +use io; +use io::prelude::*; +use io::{Error, ErrorKind, SeekFrom}; + +use byteorder::LittleEndian; +use byteorder_ext::{ReadBytesExt, WriteBytesExt}; + +use boot_sector::{format_boot_sector, BiosParameterBlock, BootSector}; +use dir::{Dir, DirRawStream}; +use file::File; +use table::{alloc_cluster, count_free_clusters, format_fat, read_fat_flags, ClusterIterator, RESERVED_FAT_ENTRIES}; +use time::{TimeProvider, DEFAULT_TIME_PROVIDER}; + +// FAT implementation based on: +// http://wiki.osdev.org/FAT +// https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html + +/// A type of FAT filesystem. +/// +/// `FatType` values are based on the size of File Allocation Table entry. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub enum FatType { + /// 12 bits per FAT entry + Fat12, + /// 16 bits per FAT entry + Fat16, + /// 32 bits per FAT entry + Fat32, +} + +impl FatType { + const FAT16_MIN_CLUSTERS: u32 = 4085; + const FAT32_MIN_CLUSTERS: u32 = 65525; + const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4; + + pub(crate) fn from_clusters(total_clusters: u32) -> FatType { + if total_clusters < Self::FAT16_MIN_CLUSTERS { + FatType::Fat12 + } else if total_clusters < Self::FAT32_MIN_CLUSTERS { + FatType::Fat16 + } else { + FatType::Fat32 + } + } + + pub(crate) fn bits_per_fat_entry(&self) -> u32 { + match self { + &FatType::Fat12 => 12, + &FatType::Fat16 => 16, + &FatType::Fat32 => 32, + } + } + + pub(crate) fn min_clusters(&self) -> u32 { + match self { + &FatType::Fat12 => 0, + &FatType::Fat16 => Self::FAT16_MIN_CLUSTERS, + &FatType::Fat32 => Self::FAT32_MIN_CLUSTERS, + } + } + + pub(crate) fn max_clusters(&self) -> u32 { + match self { + &FatType::Fat12 => Self::FAT16_MIN_CLUSTERS - 1, + &FatType::Fat16 => Self::FAT32_MIN_CLUSTERS - 1, + &FatType::Fat32 => Self::FAT32_MAX_CLUSTERS, + } + } +} + +/// A FAT volume status flags retrived from the Boot Sector and the allocation table second entry. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct FsStatusFlags { + pub(crate) dirty: bool, + pub(crate) io_error: bool, +} + +impl FsStatusFlags { + /// Checks if the volume is marked as dirty. + /// + /// Dirty flag means volume has been suddenly ejected from filesystem without unmounting. + pub fn dirty(&self) -> bool { + self.dirty + } + + /// Checks if the volume has the IO Error flag active. + pub fn io_error(&self) -> bool { + self.io_error + } + + fn encode(&self) -> u8 { + let mut res = 0u8; + if self.dirty { + res |= 1; + } + if self.io_error { + res |= 2; + } + res + } + + pub(crate) fn decode(flags: u8) -> Self { + FsStatusFlags { dirty: flags & 1 != 0, io_error: flags & 2 != 0 } + } +} + +/// A sum of `Read` and `Seek` traits. +pub trait ReadSeek: Read + Seek {} +impl ReadSeek for T {} + +/// A sum of `Read`, `Write` and `Seek` traits. +pub trait ReadWriteSeek: Read + Write + Seek {} +impl ReadWriteSeek for T {} + +#[derive(Clone, Default, Debug)] +struct FsInfoSector { + free_cluster_count: Option, + next_free_cluster: Option, + dirty: bool, +} + +impl FsInfoSector { + const LEAD_SIG: u32 = 0x41615252; + const STRUC_SIG: u32 = 0x61417272; + const TRAIL_SIG: u32 = 0xAA550000; + + fn deserialize(rdr: &mut T) -> io::Result { + let lead_sig = rdr.read_u32::()?; + if lead_sig != Self::LEAD_SIG { + return Err(Error::new(ErrorKind::Other, "invalid lead_sig in FsInfo sector")); + } + let mut reserved = [0u8; 480]; + rdr.read_exact(&mut reserved)?; + let struc_sig = rdr.read_u32::()?; + if struc_sig != Self::STRUC_SIG { + return Err(Error::new(ErrorKind::Other, "invalid struc_sig in FsInfo sector")); + } + let free_cluster_count = match rdr.read_u32::()? { + 0xFFFFFFFF => None, + // Note: value is validated in FileSystem::new function using values from BPB + n => Some(n), + }; + let next_free_cluster = match rdr.read_u32::()? { + 0xFFFFFFFF => None, + 0 | 1 => { + warn!("invalid next_free_cluster in FsInfo sector (values 0 and 1 are reserved)"); + None + }, + // Note: other values are validated in FileSystem::new function using values from BPB + n => Some(n), + }; + let mut reserved2 = [0u8; 12]; + rdr.read_exact(&mut reserved2)?; + let trail_sig = rdr.read_u32::()?; + if trail_sig != Self::TRAIL_SIG { + return Err(Error::new(ErrorKind::Other, "invalid trail_sig in FsInfo sector")); + } + Ok(FsInfoSector { free_cluster_count, next_free_cluster, dirty: false }) + } + + fn serialize(&self, wrt: &mut T) -> io::Result<()> { + wrt.write_u32::(Self::LEAD_SIG)?; + let reserved = [0u8; 480]; + wrt.write_all(&reserved)?; + wrt.write_u32::(Self::STRUC_SIG)?; + wrt.write_u32::(self.free_cluster_count.unwrap_or(0xFFFFFFFF))?; + wrt.write_u32::(self.next_free_cluster.unwrap_or(0xFFFFFFFF))?; + let reserved2 = [0u8; 12]; + wrt.write_all(&reserved2)?; + wrt.write_u32::(Self::TRAIL_SIG)?; + Ok(()) + } + + fn validate_and_fix(&mut self, total_clusters: u32) { + let max_valid_cluster_number = total_clusters + RESERVED_FAT_ENTRIES; + if let Some(n) = self.free_cluster_count { + if n > total_clusters { + warn!("invalid free_cluster_count ({}) in fs_info exceeds total cluster count ({})", n, total_clusters); + self.free_cluster_count = None; + } + } + if let Some(n) = self.next_free_cluster { + if n > max_valid_cluster_number { + warn!( + "invalid free_cluster_count ({}) in fs_info exceeds maximum cluster number ({})", + n, max_valid_cluster_number + ); + self.next_free_cluster = None; + } + } + } + + fn add_free_clusters(&mut self, free_clusters: i32) { + if let Some(n) = self.free_cluster_count { + self.free_cluster_count = Some((n as i32 + free_clusters) as u32); + self.dirty = true; + } + } + + fn set_next_free_cluster(&mut self, cluster: u32) { + self.next_free_cluster = Some(cluster); + self.dirty = true; + } + + fn set_free_cluster_count(&mut self, free_cluster_count: u32) { + self.free_cluster_count = Some(free_cluster_count); + self.dirty = true; + } +} + +/// A FAT filesystem mount options. +/// +/// Options are specified as an argument for `FileSystem::new` method. +#[derive(Copy, Clone, Debug)] +pub struct FsOptions { + pub(crate) update_accessed_date: bool, + pub(crate) oem_cp_converter: &'static OemCpConverter, + pub(crate) time_provider: &'static TimeProvider, +} + +impl FsOptions { + /// Creates a `FsOptions` struct with default options. + pub fn new() -> Self { + FsOptions { + update_accessed_date: false, + oem_cp_converter: &LOSSY_OEM_CP_CONVERTER, + time_provider: &DEFAULT_TIME_PROVIDER, + } + } + + /// If enabled accessed date field in directory entry is updated when reading or writing a file. + pub fn update_accessed_date(mut self, enabled: bool) -> Self { + self.update_accessed_date = enabled; + self + } + + /// Changes default OEM code page encoder-decoder. + pub fn oem_cp_converter(mut self, oem_cp_converter: &'static OemCpConverter) -> Self { + self.oem_cp_converter = oem_cp_converter; + self + } + + /// Changes default time provider. + pub fn time_provider(mut self, time_provider: &'static TimeProvider) -> Self { + self.time_provider = time_provider; + self + } +} + +/// A FAT volume statistics. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct FileSystemStats { + cluster_size: u32, + total_clusters: u32, + free_clusters: u32, +} + +impl FileSystemStats { + /// Cluster size in bytes + pub fn cluster_size(&self) -> u32 { + self.cluster_size + } + + /// Number of total clusters in filesystem usable for file allocation + pub fn total_clusters(&self) -> u32 { + self.total_clusters + } + + /// Number of free clusters + pub fn free_clusters(&self) -> u32 { + self.free_clusters + } +} + +/// A FAT filesystem object. +/// +/// `FileSystem` struct is representing a state of a mounted FAT volume. +pub struct FileSystem { + pub(crate) disk: RefCell, + pub(crate) options: FsOptions, + fat_type: FatType, + bpb: BiosParameterBlock, + first_data_sector: u32, + root_dir_sectors: u32, + total_clusters: u32, + fs_info: RefCell, + current_status_flags: Cell, +} + +impl FileSystem { + /// Creates a new filesystem object instance. + /// + /// Supplied `disk` parameter cannot be seeked. If there is a need to read a fragment of disk + /// image (e.g. partition) library user should wrap the file struct in a struct limiting + /// access to partition bytes only e.g. `fscommon::StreamSlice`. + /// + /// Note: creating multiple filesystem objects with one underlying device/disk image can + /// cause a filesystem corruption. + pub fn new(mut disk: T, options: FsOptions) -> io::Result { + // Make sure given image is not seeked + trace!("FileSystem::new"); + debug_assert!(disk.seek(SeekFrom::Current(0))? == 0); + + // read boot sector + let bpb = { + let boot = BootSector::deserialize(&mut disk)?; + boot.validate()?; + boot.bpb + }; + + let root_dir_sectors = bpb.root_dir_sectors(); + let first_data_sector = bpb.first_data_sector(); + let total_clusters = bpb.total_clusters(); + let fat_type = FatType::from_clusters(total_clusters); + + // read FSInfo sector if this is FAT32 + let mut fs_info = if fat_type == FatType::Fat32 { + disk.seek(SeekFrom::Start(bpb.bytes_from_sectors(bpb.fs_info_sector())))?; + FsInfoSector::deserialize(&mut disk)? + } else { + FsInfoSector::default() + }; + + // if dirty flag is set completly ignore free_cluster_count in FSInfo + if bpb.status_flags().dirty { + fs_info.free_cluster_count = None; + } + + // Validate the numbers stored in the free_cluster_count and next_free_cluster are within bounds for volume + fs_info.validate_and_fix(total_clusters); + + // return FileSystem struct + let status_flags = bpb.status_flags(); + trace!("FileSystem::new end"); + Ok(FileSystem { + disk: RefCell::new(disk), + options, + fat_type, + bpb, + first_data_sector, + root_dir_sectors, + total_clusters, + fs_info: RefCell::new(fs_info), + current_status_flags: Cell::new(status_flags), + }) + } + + /// Returns a type of File Allocation Table (FAT) used by this filesystem. + pub fn fat_type(&self) -> FatType { + self.fat_type + } + + /// Returns a volume identifier read from BPB in the Boot Sector. + pub fn volume_id(&self) -> u32 { + self.bpb.volume_id + } + + /// Returns a volume label from BPB in the Boot Sector as `String`. + /// + /// Non-ASCII characters are replaced by the replacement character (U+FFFD). + /// Note: This function returns label stored in the BPB structure. Use `read_volume_label_from_root_dir` to read + /// label from the root directory. + #[cfg(feature = "alloc")] + pub fn volume_label(&self) -> String { + // Decode volume label from OEM codepage + let volume_label_iter = self.volume_label_as_bytes().iter().cloned(); + let char_iter = volume_label_iter.map(|c| self.options.oem_cp_converter.decode(c)); + // Build string from character iterator + String::from_iter(char_iter) + } + + /// Returns a volume label from BPB in the Boot Sector as byte array slice. + /// + /// Label is encoded in the OEM codepage. + /// Note: This function returns label stored in the BPB structure. Use `read_volume_label_from_root_dir_as_bytes` + /// to read label from the root directory. + pub fn volume_label_as_bytes(&self) -> &[u8] { + const PADDING: u8 = 0x20; + let full_label_slice = &self.bpb.volume_label; + let len = full_label_slice.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0); + &full_label_slice[..len] + } + + /// Returns a volume label from root directory as `String`. + /// + /// It finds file with `VOLUME_ID` attribute and returns its short name. + #[cfg(feature = "alloc")] + pub fn read_volume_label_from_root_dir(&self) -> io::Result> { + // Note: DirEntry::file_short_name() cannot be used because it interprets name as 8.3 + // (adds dot before an extension) + let volume_label_opt = self.read_volume_label_from_root_dir_as_bytes()?; + if let Some(volume_label) = volume_label_opt { + const PADDING: u8 = 0x20; + // Strip label padding + let len = volume_label.iter().rposition(|b| *b != PADDING).map(|p| p + 1).unwrap_or(0); + let label_slice = &volume_label[..len]; + // Decode volume label from OEM codepage + let volume_label_iter = label_slice.iter().cloned(); + let char_iter = volume_label_iter.map(|c| self.options.oem_cp_converter.decode(c)); + // Build string from character iterator + Ok(Some(String::from_iter(char_iter))) + } else { + Ok(None) + } + } + + /// Returns a volume label from root directory as byte array. + /// + /// Label is encoded in the OEM codepage. + /// It finds file with `VOLUME_ID` attribute and returns its short name. + pub fn read_volume_label_from_root_dir_as_bytes(&self) -> io::Result> { + let entry_opt = self.root_dir().find_volume_entry()?; + Ok(entry_opt.map(|e| *e.raw_short_name())) + } + + /// Returns a root directory object allowing for futher penetration of a filesystem structure. + pub fn root_dir<'b>(&'b self) -> Dir<'b, T> { + trace!("root_dir"); + let root_rdr = { + match self.fat_type { + FatType::Fat12 | FatType::Fat16 => DirRawStream::Root(DiskSlice::from_sectors( + self.first_data_sector - self.root_dir_sectors, + self.root_dir_sectors, + 1, + &self.bpb, + FsIoAdapter { fs: self }, + )), + _ => DirRawStream::File(File::new(Some(self.bpb.root_dir_first_cluster), None, self)), + } + }; + Dir::new(root_rdr, self) + } + + fn offset_from_sector(&self, sector: u32) -> u64 { + self.bpb.bytes_from_sectors(sector) + } + + fn sector_from_cluster(&self, cluster: u32) -> u32 { + self.first_data_sector + self.bpb.sectors_from_clusters(cluster - RESERVED_FAT_ENTRIES) + } + + pub fn cluster_size(&self) -> u32 { + self.bpb.cluster_size() + } + + pub(crate) fn offset_from_cluster(&self, cluser: u32) -> u64 { + self.offset_from_sector(self.sector_from_cluster(cluser)) + } + + pub(crate) fn bytes_from_clusters(&self, clusters: u32) -> u64 { + self.bpb.bytes_from_sectors(self.bpb.sectors_from_clusters(clusters)) + } + + pub(crate) fn clusters_from_bytes(&self, bytes: u64) -> u32 { + self.bpb.clusters_from_bytes(bytes) + } + + fn fat_slice<'b>(&'b self) -> DiskSlice> { + let io = FsIoAdapter { fs: self }; + fat_slice(io, &self.bpb) + } + + pub(crate) fn cluster_iter<'b>(&'b self, cluster: u32) -> ClusterIterator>> { + let disk_slice = self.fat_slice(); + ClusterIterator::new(disk_slice, self.fat_type, cluster) + } + + pub(crate) fn truncate_cluster_chain(&self, cluster: u32) -> io::Result<()> { + let mut iter = self.cluster_iter(cluster); + let num_free = iter.truncate()?; + let mut fs_info = self.fs_info.borrow_mut(); + fs_info.add_free_clusters(num_free as i32); + Ok(()) + } + + pub(crate) fn free_cluster_chain(&self, cluster: u32) -> io::Result<()> { + let mut iter = self.cluster_iter(cluster); + let num_free = iter.free()?; + let mut fs_info = self.fs_info.borrow_mut(); + fs_info.add_free_clusters(num_free as i32); + Ok(()) + } + + pub(crate) fn alloc_cluster(&self, prev_cluster: Option, zero: bool) -> io::Result { + trace!("alloc_cluster"); + let hint = self.fs_info.borrow().next_free_cluster; + let cluster = { + let mut fat = self.fat_slice(); + alloc_cluster(&mut fat, self.fat_type, prev_cluster, hint, self.total_clusters)? + }; + if zero { + let mut disk = self.disk.borrow_mut(); + disk.seek(SeekFrom::Start(self.offset_from_cluster(cluster)))?; + write_zeros(&mut *disk, self.cluster_size() as u64)?; + } + let mut fs_info = self.fs_info.borrow_mut(); + fs_info.set_next_free_cluster(cluster + 1); + fs_info.add_free_clusters(-1); + Ok(cluster) + } + + /// Returns status flags for this volume. + pub fn read_status_flags(&self) -> io::Result { + let bpb_status = self.bpb.status_flags(); + let fat_status = read_fat_flags(&mut self.fat_slice(), self.fat_type)?; + Ok(FsStatusFlags { + dirty: bpb_status.dirty || fat_status.dirty, + io_error: bpb_status.io_error || fat_status.io_error, + }) + } + + /// Returns filesystem statistics like number of total and free clusters. + /// + /// For FAT32 volumes number of free clusters from FSInfo sector is returned (may be incorrect). + /// For other FAT variants number is computed on the first call to this method and cached for later use. + pub fn stats(&self) -> io::Result { + let free_clusters_option = self.fs_info.borrow().free_cluster_count; + let free_clusters = match free_clusters_option { + Some(n) => n, + _ => self.recalc_free_clusters()?, + }; + Ok(FileSystemStats { cluster_size: self.cluster_size(), total_clusters: self.total_clusters, free_clusters }) + } + + /// Forces free clusters recalculation. + fn recalc_free_clusters(&self) -> io::Result { + let mut fat = self.fat_slice(); + let free_cluster_count = count_free_clusters(&mut fat, self.fat_type, self.total_clusters)?; + self.fs_info.borrow_mut().set_free_cluster_count(free_cluster_count); + Ok(free_cluster_count) + } + + /// Unmounts the filesystem. + /// + /// Updates FSInfo sector if needed. + pub fn unmount(self) -> io::Result<()> { + self.unmount_internal() + } + + fn unmount_internal(&self) -> io::Result<()> { + self.flush_fs_info()?; + self.set_dirty_flag(false)?; + Ok(()) + } + + fn flush_fs_info(&self) -> io::Result<()> { + let mut fs_info = self.fs_info.borrow_mut(); + if self.fat_type == FatType::Fat32 && fs_info.dirty { + let mut disk = self.disk.borrow_mut(); + disk.seek(SeekFrom::Start(self.offset_from_sector(self.bpb.fs_info_sector as u32)))?; + fs_info.serialize(&mut *disk)?; + fs_info.dirty = false; + } + Ok(()) + } + + pub(crate) fn set_dirty_flag(&self, dirty: bool) -> io::Result<()> { + // Do not overwrite flags read from BPB on mount + let mut flags = self.bpb.status_flags(); + flags.dirty |= dirty; + // Check if flags has changed + let current_flags = self.current_status_flags.get(); + if flags == current_flags { + // Nothing to do + return Ok(()); + } + let encoded = flags.encode(); + // Note: only one field is written to avoid rewriting entire boot-sector which could be dangerous + // Compute reserver_1 field offset and write new flags + let offset = if self.fat_type() == FatType::Fat32 { 0x041 } else { 0x025 }; + let mut disk = self.disk.borrow_mut(); + disk.seek(io::SeekFrom::Start(offset))?; + disk.write_u8(encoded)?; + self.current_status_flags.set(flags); + Ok(()) + } +} + +/// `Drop` implementation tries to unmount the filesystem when dropping. +impl Drop for FileSystem { + fn drop(&mut self) { + if let Err(err) = self.unmount_internal() { + error!("unmount failed {}", err); + } + } +} + +pub(crate) struct FsIoAdapter<'a, T: ReadWriteSeek + 'a> { + fs: &'a FileSystem, +} + +impl<'a, T: ReadWriteSeek> Read for FsIoAdapter<'a, T> { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + self.fs.disk.borrow_mut().read(buf) + } +} + +impl<'a, T: ReadWriteSeek> Write for FsIoAdapter<'a, T> { + fn write(&mut self, buf: &[u8]) -> io::Result { + let size = self.fs.disk.borrow_mut().write(buf)?; + if size > 0 { + self.fs.set_dirty_flag(true)?; + } + Ok(size) + } + + fn flush(&mut self) -> io::Result<()> { + self.fs.disk.borrow_mut().flush() + } +} + +impl<'a, T: ReadWriteSeek> Seek for FsIoAdapter<'a, T> { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + self.fs.disk.borrow_mut().seek(pos) + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl<'a, T: ReadWriteSeek> Clone for FsIoAdapter<'a, T> { + fn clone(&self) -> Self { + FsIoAdapter { fs: self.fs } + } +} + +fn fat_slice(io: T, bpb: &BiosParameterBlock) -> DiskSlice { + let sectors_per_fat = bpb.sectors_per_fat(); + let mirroring_enabled = bpb.mirroring_enabled(); + let (fat_first_sector, mirrors) = if mirroring_enabled { + (bpb.reserved_sectors(), bpb.fats) + } else { + let active_fat = bpb.active_fat() as u32; + let fat_first_sector = (bpb.reserved_sectors()) + active_fat * sectors_per_fat; + (fat_first_sector, 1) + }; + DiskSlice::from_sectors(fat_first_sector, sectors_per_fat, mirrors, bpb, io) +} + +pub(crate) struct DiskSlice { + begin: u64, + size: u64, + offset: u64, + mirrors: u8, + inner: T, +} + +impl DiskSlice { + pub(crate) fn new(begin: u64, size: u64, mirrors: u8, inner: T) -> Self { + DiskSlice { begin, size, mirrors, inner, offset: 0 } + } + + fn from_sectors(first_sector: u32, sector_count: u32, mirrors: u8, bpb: &BiosParameterBlock, inner: T) -> Self { + Self::new(bpb.bytes_from_sectors(first_sector), bpb.bytes_from_sectors(sector_count), mirrors, inner) + } + + pub(crate) fn abs_pos(&self) -> u64 { + self.begin + self.offset + } +} + +// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925 +impl Clone for DiskSlice { + fn clone(&self) -> Self { + DiskSlice { + begin: self.begin, + size: self.size, + offset: self.offset, + mirrors: self.mirrors, + inner: self.inner.clone(), + } + } +} + +impl<'a, T: Read + Seek> Read for DiskSlice { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let offset = self.begin + self.offset; + let read_size = cmp::min((self.size - self.offset) as usize, buf.len()); + self.inner.seek(SeekFrom::Start(offset))?; + let size = self.inner.read(&mut buf[..read_size])?; + self.offset += size as u64; + Ok(size) + } +} + +impl<'a, T: Write + Seek> Write for DiskSlice { + fn write(&mut self, buf: &[u8]) -> io::Result { + let offset = self.begin + self.offset; + let write_size = cmp::min((self.size - self.offset) as usize, buf.len()); + if write_size == 0 { + return Ok(0); + } + // Write data + for i in 0..self.mirrors { + self.inner.seek(SeekFrom::Start(offset + i as u64 * self.size))?; + self.inner.write_all(&buf[..write_size])?; + } + self.offset += write_size as u64; + Ok(write_size) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl<'a, T> Seek for DiskSlice { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let new_offset = match pos { + SeekFrom::Current(x) => self.offset as i64 + x, + SeekFrom::Start(x) => x as i64, + SeekFrom::End(x) => self.size as i64 + x, + }; + if new_offset < 0 || new_offset as u64 > self.size { + Err(io::Error::new(ErrorKind::InvalidInput, "Seek to a negative offset")) + } else { + self.offset = new_offset as u64; + Ok(self.offset) + } + } +} + +/// An OEM code page encoder/decoder. +/// +/// Provides a custom implementation for a short name encoding/decoding. +/// Default implementation changes all non-ASCII characters to the replacement character (U+FFFD). +/// `OemCpConverter` is specified by the `oem_cp_converter` property in `FsOptions` struct. +pub trait OemCpConverter: Debug { + fn decode(&self, oem_char: u8) -> char; + fn encode(&self, uni_char: char) -> Option; +} + +#[derive(Debug)] +pub(crate) struct LossyOemCpConverter { + _dummy: (), +} + +impl OemCpConverter for LossyOemCpConverter { + fn decode(&self, oem_char: u8) -> char { + if oem_char <= 0x7F { + oem_char as char + } else { + '\u{FFFD}' + } + } + fn encode(&self, uni_char: char) -> Option { + if uni_char <= '\x7F' { + Some(uni_char as u8) + } else { + None + } + } +} + +pub(crate) static LOSSY_OEM_CP_CONVERTER: LossyOemCpConverter = LossyOemCpConverter { _dummy: () }; + +pub(crate) fn write_zeros(mut disk: T, mut len: u64) -> io::Result<()> { + const ZEROS: [u8; 512] = [0u8; 512]; + while len > 0 { + let write_size = cmp::min(len, ZEROS.len() as u64) as usize; + disk.write_all(&ZEROS[..write_size])?; + len -= write_size as u64; + } + Ok(()) +} + +fn write_zeros_until_end_of_sector(mut disk: T, bytes_per_sector: u16) -> io::Result<()> { + let pos = disk.seek(SeekFrom::Current(0))?; + let total_bytes_to_write = bytes_per_sector as u64 - (pos % bytes_per_sector as u64); + if total_bytes_to_write != bytes_per_sector as u64 { + write_zeros(disk, total_bytes_to_write)?; + } + Ok(()) +} + +/// A FAT filesystem formatting options +/// +/// This struct implements a builder pattern. +/// Options are specified as an argument for `format_volume` function. +#[derive(Default, Debug, Clone)] +pub struct FormatVolumeOptions { + pub(crate) bytes_per_sector: Option, + pub(crate) total_sectors: Option, + pub(crate) bytes_per_cluster: Option, + pub(crate) fat_type: Option, + pub(crate) max_root_dir_entries: Option, + pub(crate) fats: Option, + pub(crate) media: Option, + pub(crate) sectors_per_track: Option, + pub(crate) heads: Option, + pub(crate) drive_num: Option, + pub(crate) volume_id: Option, + pub(crate) volume_label: Option<[u8; 11]>, +} + +impl FormatVolumeOptions { + /// Create options struct for `format_volume` function + /// + /// Allows to overwrite many filesystem parameters. + /// In normal use-case defaults should suffice. + pub fn new() -> Self { + FormatVolumeOptions { ..Default::default() } + } + + /// Set size of cluster in bytes (must be dividable by sector size) + /// + /// Cluster size must be a power of two and be greater or equal to sector size. + /// If option is not specified optimal cluster size is selected based on partition size and + /// optionally FAT type override (if specified using `fat_type` method). + pub fn bytes_per_cluster(mut self, bytes_per_cluster: u32) -> Self { + assert!(bytes_per_cluster.count_ones() == 1 && bytes_per_cluster >= 512, "Invalid bytes_per_cluster"); + self.bytes_per_cluster = Some(bytes_per_cluster); + self + } + + /// Set File Allocation Table type + /// + /// Option allows to override File Allocation Table (FAT) entry size. + /// It is unrecommended to set this option unless you know what you are doing. + /// Note: FAT type is determined from total number of clusters. Changing this option can cause formatting to fail + /// if the volume cannot be divided into proper number of clusters for selected FAT type. + pub fn fat_type(mut self, fat_type: FatType) -> Self { + self.fat_type = Some(fat_type); + self + } + + /// Set sector size in bytes + /// + /// Sector size must be a power of two and be in range 512 - 4096. + /// Default is `512`. + pub fn bytes_per_sector(mut self, bytes_per_sector: u16) -> Self { + assert!(bytes_per_sector.count_ones() == 1 && bytes_per_sector >= 512, "Invalid bytes_per_sector"); + self.bytes_per_sector = Some(bytes_per_sector); + self + } + + /// Set total number of sectors + /// + /// If option is not specified total number of sectors is calculated as storage device size divided by sector size. + pub fn total_sectors(mut self, total_sectors: u32) -> Self { + self.total_sectors = Some(total_sectors); + self + } + + /// Set maximal numer of entries in root directory for FAT12/FAT16 volumes + /// + /// Total root directory size should be dividable by sectors size so keep it a multiple of 16 (for default sector + /// size). + /// Note: this limit is not used on FAT32 volumes. + /// Default is `512`. + pub fn max_root_dir_entries(mut self, max_root_dir_entries: u16) -> Self { + self.max_root_dir_entries = Some(max_root_dir_entries); + self + } + + /// Set number of File Allocation Tables + /// + /// The only allowed values are `1` and `2`. If value `2` is used the FAT is mirrored. + /// Default is `2`. + pub fn fats(mut self, fats: u8) -> Self { + assert!(fats >= 1 && fats <= 2, "Invalid number of FATs"); + self.fats = Some(fats); + self + } + + /// Set media field for Bios Parameters Block + /// + /// Default is `0xF8`. + pub fn media(mut self, media: u8) -> Self { + self.media = Some(media); + self + } + + /// Set number of physical sectors per track for Bios Parameters Block (INT 13h CHS geometry) + /// + /// Default is `0x20`. + pub fn sectors_per_track(mut self, sectors_per_track: u16) -> Self { + self.sectors_per_track = Some(sectors_per_track); + self + } + + /// Set number of heads for Bios Parameters Block (INT 13h CHS geometry) + /// + /// Default is `0x40`. + pub fn heads(mut self, heads: u16) -> Self { + self.heads = Some(heads); + self + } + + /// Set drive number for Bios Parameters Block + /// + /// Default is `0` for FAT12, `0x80` for FAT16/FAT32. + pub fn drive_num(mut self, drive_num: u8) -> Self { + self.drive_num = Some(drive_num); + self + } + + /// Set volume ID for Bios Parameters Block + /// + /// Default is `0x12345678`. + pub fn volume_id(mut self, volume_id: u32) -> Self { + self.volume_id = Some(volume_id); + self + } + + /// Set volume label + /// + /// Default is empty label. + pub fn volume_label(mut self, volume_label: [u8; 11]) -> Self { + self.volume_label = Some(volume_label); + self + } +} + +/// Create FAT filesystem on a disk or partition (format a volume) +/// +/// Warning: this function overrides internal FAT filesystem structures and causes a loss of all data on provided +/// partition. Please use it with caution. +/// Only quick formatting is supported. To achieve a full format zero entire partition before calling this function. +/// Supplied `disk` parameter cannot be seeked (internal pointer must be on position 0). +/// To format a fragment of a disk image (e.g. partition) library user should wrap the file struct in a struct +/// limiting access to partition bytes only e.g. `fscommon::StreamSlice`. +pub fn format_volume(mut disk: T, options: FormatVolumeOptions) -> io::Result<()> { + trace!("format_volume"); + debug_assert!(disk.seek(SeekFrom::Current(0))? == 0); + + let bytes_per_sector = options.bytes_per_sector.unwrap_or(512); + let total_sectors = if options.total_sectors.is_none() { + let total_bytes: u64 = disk.seek(SeekFrom::End(0))?; + let total_sectors_64 = total_bytes / u64::from(bytes_per_sector); + disk.seek(SeekFrom::Start(0))?; + if total_sectors_64 > u64::from(u32::MAX) { + return Err(Error::new(ErrorKind::Other, "Volume has too many sectors")); + } + total_sectors_64 as u32 + } else { + options.total_sectors.unwrap() // SAFE: checked above + }; + + // Create boot sector, validate and write to storage device + let (boot, fat_type) = format_boot_sector(&options, total_sectors, bytes_per_sector)?; + boot.validate()?; + boot.serialize(&mut disk)?; + // Make sure entire logical sector is updated (serialize method always writes 512 bytes) + let bytes_per_sector = boot.bpb.bytes_per_sector; + write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?; + + if boot.bpb.is_fat32() { + // FSInfo sector + let fs_info_sector = FsInfoSector { free_cluster_count: None, next_free_cluster: None, dirty: false }; + disk.seek(SeekFrom::Start(boot.bpb.bytes_from_sectors(boot.bpb.fs_info_sector())))?; + fs_info_sector.serialize(&mut disk)?; + write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?; + + // backup boot sector + disk.seek(SeekFrom::Start(boot.bpb.bytes_from_sectors(boot.bpb.backup_boot_sector())))?; + boot.serialize(&mut disk)?; + write_zeros_until_end_of_sector(&mut disk, bytes_per_sector)?; + } + + // format File Allocation Table + let reserved_sectors = boot.bpb.reserved_sectors(); + let fat_pos = boot.bpb.bytes_from_sectors(reserved_sectors); + let sectors_per_all_fats = boot.bpb.sectors_per_all_fats(); + disk.seek(SeekFrom::Start(fat_pos))?; + write_zeros(&mut disk, boot.bpb.bytes_from_sectors(sectors_per_all_fats))?; + { + let mut fat_slice = fat_slice(&mut disk, &boot.bpb); + let sectors_per_fat = boot.bpb.sectors_per_fat(); + let bytes_per_fat = boot.bpb.bytes_from_sectors(sectors_per_fat); + format_fat(&mut fat_slice, fat_type, boot.bpb.media, bytes_per_fat, boot.bpb.total_clusters())?; + } + + // init root directory - zero root directory region for FAT12/16 and alloc first root directory cluster for FAT32 + let root_dir_first_sector = reserved_sectors + sectors_per_all_fats; + let root_dir_sectors = boot.bpb.root_dir_sectors(); + let root_dir_pos = boot.bpb.bytes_from_sectors(root_dir_first_sector); + disk.seek(SeekFrom::Start(root_dir_pos))?; + write_zeros(&mut disk, boot.bpb.bytes_from_sectors(root_dir_sectors))?; + if fat_type == FatType::Fat32 { + let root_dir_first_cluster = { + let mut fat_slice = fat_slice(&mut disk, &boot.bpb); + alloc_cluster(&mut fat_slice, fat_type, None, None, 1)? + }; + assert!(root_dir_first_cluster == boot.bpb.root_dir_first_cluster); + let first_data_sector = reserved_sectors + sectors_per_all_fats + root_dir_sectors; + let root_dir_first_sector = + first_data_sector + boot.bpb.sectors_from_clusters(root_dir_first_cluster - RESERVED_FAT_ENTRIES); + let root_dir_pos = boot.bpb.bytes_from_sectors(root_dir_first_sector); + disk.seek(SeekFrom::Start(root_dir_pos))?; + write_zeros(&mut disk, boot.bpb.cluster_size() as u64)?; + } + + // TODO: create volume label dir entry if volume label is set + + disk.seek(SeekFrom::Start(0))?; + trace!("format_volume end"); + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..3e4531c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,112 @@ +//! A FAT filesystem library implemented in Rust. +//! +//! # Usage +//! +//! This crate is [on crates.io](https://crates.io/crates/fatfs) and can be +//! used by adding `fatfs` to the dependencies in your project's `Cargo.toml`. +//! +//! ```toml +//! [dependencies] +//! fatfs = "0.3" +//! ``` +//! +//! And this in your crate root: +//! +//! ```rust +//! extern crate fatfs; +//! ``` +//! +//! # Examples +//! +//! ```rust +//! // Declare external crates +//! // Note: `fscommon` crate is used to speedup IO operations +//! extern crate fatfs; +//! extern crate fscommon; +//! +//! use std::io::prelude::*; +//! +//! fn main() -> std::io::Result<()> { +//! # std::fs::copy("resources/fat16.img", "tmp/fat.img")?; +//! // Initialize a filesystem object +//! let img_file = std::fs::OpenOptions::new().read(true).write(true) +//! .open("tmp/fat.img")?; +//! let buf_stream = fscommon::BufStream::new(img_file); +//! let fs = fatfs::FileSystem::new(buf_stream, fatfs::FsOptions::new())?; +//! let root_dir = fs.root_dir(); +//! +//! // Write a file +//! root_dir.create_dir("foo")?; +//! let mut file = root_dir.create_file("foo/hello.txt")?; +//! file.truncate()?; +//! file.write_all(b"Hello World!")?; +//! +//! // Read a directory +//! let dir = root_dir.open_dir("foo")?; +//! for r in dir.iter() { +//! let entry = r?; +//! println!("{}", entry.file_name()); +//! } +//! # std::fs::remove_file("tmp/fat.img")?; +//! # Ok(()) +//! } +//! ``` + +#![crate_type = "lib"] +#![crate_name = "fatfs"] +#![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(all(not(feature = "std"), feature = "alloc"), feature(alloc))] +// Disable warnings to not clutter code with cfg too much +#![cfg_attr(not(feature = "alloc"), allow(dead_code, unused_imports))] +// Inclusive ranges requires Rust 1.26.0 +#![allow(ellipsis_inclusive_range_patterns)] +// `dyn` syntax requires Rust 1.27.0 +#![allow(bare_trait_objects)] +// `alloc` compiler feature is needed in Rust before 1.36 +#![cfg_attr(all(not(feature = "std"), feature = "alloc"), allow(stable_features))] + +extern crate byteorder; + +#[macro_use] +extern crate bitflags; + +#[macro_use] +extern crate log; + +#[cfg(feature = "chrono")] +extern crate chrono; + +#[cfg(not(feature = "std"))] +extern crate core_io; + +#[cfg(all(not(feature = "std"), feature = "alloc"))] +extern crate alloc; + +mod boot_sector; +mod dir; +mod dir_entry; +mod file; +mod fs; +mod table; +mod time; + +#[cfg(not(feature = "std"))] +mod byteorder_core_io; + +#[cfg(feature = "std")] +use byteorder as byteorder_ext; +#[cfg(not(feature = "std"))] +use byteorder_core_io as byteorder_ext; +#[cfg(not(feature = "std"))] +use core_io as io; +#[cfg(feature = "std")] +use std as core; + +#[cfg(feature = "std")] +use std::io; + +pub use dir::*; +pub use dir_entry::*; +pub use file::*; +pub use fs::*; +pub use time::*; diff --git a/src/table.rs b/src/table.rs new file mode 100644 index 0000000..5b0c957 --- /dev/null +++ b/src/table.rs @@ -0,0 +1,564 @@ +use byteorder::LittleEndian; +use byteorder_ext::{ReadBytesExt, WriteBytesExt}; +use core::cmp; +use io; + +use fs::{FatType, FsStatusFlags, ReadSeek, ReadWriteSeek}; + +struct Fat { + #[allow(dead_code)] + dummy: [T; 0], +} + +type Fat12 = Fat; +type Fat16 = Fat; +type Fat32 = Fat; + +pub const RESERVED_FAT_ENTRIES: u32 = 2; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum FatValue { + Free, + Data(u32), + Bad, + EndOfChain, +} + +trait FatTrait { + fn get_raw(fat: &mut T, cluster: u32) -> io::Result; + fn get(fat: &mut T, cluster: u32) -> io::Result; + fn set_raw(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()>; + fn set(fat: &mut T, cluster: u32, value: FatValue) -> io::Result<()>; + fn find_free(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result; + fn count_free(fat: &mut T, end_cluster: u32) -> io::Result; +} + +fn read_fat(fat: &mut T, fat_type: FatType, cluster: u32) -> io::Result { + match fat_type { + FatType::Fat12 => Fat12::get(fat, cluster), + FatType::Fat16 => Fat16::get(fat, cluster), + FatType::Fat32 => Fat32::get(fat, cluster), + } +} + +fn write_fat(fat: &mut T, fat_type: FatType, cluster: u32, value: FatValue) -> io::Result<()> { + trace!("write FAT - cluster {} value {:?}", cluster, value); + match fat_type { + FatType::Fat12 => Fat12::set(fat, cluster, value), + FatType::Fat16 => Fat16::set(fat, cluster, value), + FatType::Fat32 => Fat32::set(fat, cluster, value), + } +} + +fn get_next_cluster(fat: &mut T, fat_type: FatType, cluster: u32) -> io::Result> { + let val = read_fat(fat, fat_type, cluster)?; + match val { + FatValue::Data(n) => Ok(Some(n)), + _ => Ok(None), + } +} + +fn find_free_cluster( + fat: &mut T, + fat_type: FatType, + start_cluster: u32, + end_cluster: u32, +) -> io::Result { + match fat_type { + FatType::Fat12 => Fat12::find_free(fat, start_cluster, end_cluster), + FatType::Fat16 => Fat16::find_free(fat, start_cluster, end_cluster), + FatType::Fat32 => Fat32::find_free(fat, start_cluster, end_cluster), + } +} + +pub(crate) fn alloc_cluster( + fat: &mut T, + fat_type: FatType, + prev_cluster: Option, + hint: Option, + total_clusters: u32, +) -> io::Result { + let end_cluster = total_clusters + RESERVED_FAT_ENTRIES; + let start_cluster = match hint { + Some(n) if n < end_cluster => n, + _ => RESERVED_FAT_ENTRIES, + }; + let new_cluster = match find_free_cluster(fat, fat_type, start_cluster, end_cluster) { + Ok(n) => n, + Err(_) if start_cluster > RESERVED_FAT_ENTRIES => { + find_free_cluster(fat, fat_type, RESERVED_FAT_ENTRIES, start_cluster)? + }, + Err(e) => return Err(e), + }; + write_fat(fat, fat_type, new_cluster, FatValue::EndOfChain)?; + if let Some(n) = prev_cluster { + write_fat(fat, fat_type, n, FatValue::Data(new_cluster))?; + } + trace!("allocated cluster {}", new_cluster); + Ok(new_cluster) +} + +pub(crate) fn read_fat_flags(fat: &mut T, fat_type: FatType) -> io::Result { + // check MSB (except in FAT12) + let val = match fat_type { + FatType::Fat12 => 0xFFF, + FatType::Fat16 => Fat16::get_raw(fat, 1)?, + FatType::Fat32 => Fat32::get_raw(fat, 1)?, + }; + let dirty = match fat_type { + FatType::Fat12 => false, + FatType::Fat16 => val & (1 << 15) == 0, + FatType::Fat32 => val & (1 << 27) == 0, + }; + let io_error = match fat_type { + FatType::Fat12 => false, + FatType::Fat16 => val & (1 << 14) == 0, + FatType::Fat32 => val & (1 << 26) == 0, + }; + Ok(FsStatusFlags { dirty, io_error }) +} + +pub(crate) fn count_free_clusters(fat: &mut T, fat_type: FatType, total_clusters: u32) -> io::Result { + let end_cluster = total_clusters + RESERVED_FAT_ENTRIES; + match fat_type { + FatType::Fat12 => Fat12::count_free(fat, end_cluster), + FatType::Fat16 => Fat16::count_free(fat, end_cluster), + FatType::Fat32 => Fat32::count_free(fat, end_cluster), + } +} + +pub(crate) fn format_fat( + fat: &mut T, + fat_type: FatType, + media: u8, + bytes_per_fat: u64, + total_clusters: u32, +) -> io::Result<()> { + // init first two reserved entries to FAT ID + match fat_type { + FatType::Fat12 => { + fat.write_u8(media)?; + fat.write_u16::(0xFFFF)?; + }, + FatType::Fat16 => { + fat.write_u16::(media as u16 | 0xFF00)?; + fat.write_u16::(0xFFFF)?; + }, + FatType::Fat32 => { + fat.write_u32::(media as u32 | 0xFFFFF00)?; + fat.write_u32::(0xFFFFFFFF)?; + }, + }; + // mark entries at the end of FAT as used (after FAT but before sector end) + const BITS_PER_BYTE: u64 = 8; + let start_cluster = total_clusters + RESERVED_FAT_ENTRIES; + let end_cluster = (bytes_per_fat * BITS_PER_BYTE / fat_type.bits_per_fat_entry() as u64) as u32; + for cluster in start_cluster..end_cluster { + write_fat(fat, fat_type, cluster, FatValue::EndOfChain)?; + } + // mark special entries 0x0FFFFFF0 - 0x0FFFFFFF as BAD if they exists on FAT32 volume + if end_cluster > 0x0FFFFFF0 { + let end_bad_cluster = cmp::min(0x0FFFFFFF + 1, end_cluster); + for cluster in 0x0FFFFFF0..end_bad_cluster { + write_fat(fat, fat_type, cluster, FatValue::Bad)?; + } + } + Ok(()) +} + +impl FatTrait for Fat12 { + fn get_raw(fat: &mut T, cluster: u32) -> io::Result { + let fat_offset = cluster + (cluster / 2); + fat.seek(io::SeekFrom::Start(fat_offset as u64))?; + let packed_val = fat.read_u16::()?; + Ok(match cluster & 1 { + 0 => packed_val & 0x0FFF, + _ => packed_val >> 4, + } as u32) + } + + fn get(fat: &mut T, cluster: u32) -> io::Result { + let val = Self::get_raw(fat, cluster)?; + Ok(match val { + 0 => FatValue::Free, + 0xFF7 => FatValue::Bad, + 0xFF8...0xFFF => FatValue::EndOfChain, + n => FatValue::Data(n as u32), + }) + } + + fn set(fat: &mut T, cluster: u32, value: FatValue) -> io::Result<()> { + let raw_val = match value { + FatValue::Free => 0, + FatValue::Bad => 0xFF7, + FatValue::EndOfChain => 0xFFF, + FatValue::Data(n) => n as u16, + }; + Self::set_raw(fat, cluster, raw_val as u32) + } + + fn set_raw(fat: &mut T, cluster: u32, raw_val: u32) -> io::Result<()> { + let fat_offset = cluster + (cluster / 2); + fat.seek(io::SeekFrom::Start(fat_offset as u64))?; + let old_packed = fat.read_u16::()?; + fat.seek(io::SeekFrom::Start(fat_offset as u64))?; + let new_packed = match cluster & 1 { + 0 => (old_packed & 0xF000) | raw_val as u16, + _ => (old_packed & 0x000F) | ((raw_val as u16) << 4), + }; + fat.write_u16::(new_packed)?; + Ok(()) + } + + fn find_free(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result { + let mut cluster = start_cluster; + let fat_offset = cluster + (cluster / 2); + fat.seek(io::SeekFrom::Start(fat_offset as u64))?; + let mut packed_val = fat.read_u16::()?; + loop { + let val = match cluster & 1 { + 0 => packed_val & 0x0FFF, + _ => packed_val >> 4, + }; + if val == 0 { + return Ok(cluster); + } + cluster += 1; + if cluster == end_cluster { + return Err(io::Error::new(io::ErrorKind::Other, "No space left on device")); + } + packed_val = match cluster & 1 { + 0 => fat.read_u16::()?, + _ => { + let next_byte = fat.read_u8()? as u16; + (packed_val >> 8) | (next_byte << 8) + }, + }; + } + } + + fn count_free(fat: &mut T, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster * 3 / 2) as u64))?; + let mut prev_packed_val = 0u16; + while cluster < end_cluster { + let res = match cluster & 1 { + 0 => fat.read_u16::(), + _ => fat.read_u8().map(|n| n as u16), + }; + let packed_val = match res { + Err(err) => return Err(err), + Ok(n) => n, + }; + let val = match cluster & 1 { + 0 => packed_val & 0x0FFF, + _ => (packed_val << 8) | (prev_packed_val >> 12), + }; + prev_packed_val = packed_val; + if val == 0 { + count += 1; + } + cluster += 1; + } + Ok(count) + } +} + +impl FatTrait for Fat16 { + fn get_raw(fat: &mut T, cluster: u32) -> io::Result { + fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?; + Ok(fat.read_u16::()? as u32) + } + + fn get(fat: &mut T, cluster: u32) -> io::Result { + let val = Self::get_raw(fat, cluster)?; + Ok(match val { + 0 => FatValue::Free, + 0xFFF7 => FatValue::Bad, + 0xFFF8...0xFFFF => FatValue::EndOfChain, + n => FatValue::Data(n as u32), + }) + } + + fn set_raw(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()> { + fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?; + fat.write_u16::(raw_value as u16)?; + Ok(()) + } + + fn set(fat: &mut T, cluster: u32, value: FatValue) -> io::Result<()> { + let raw_value = match value { + FatValue::Free => 0, + FatValue::Bad => 0xFFF7, + FatValue::EndOfChain => 0xFFFF, + FatValue::Data(n) => n as u16, + }; + Self::set_raw(fat, cluster, raw_value as u32) + } + + fn find_free(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result { + let mut cluster = start_cluster; + fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?; + while cluster < end_cluster { + let val = fat.read_u16::()?; + if val == 0 { + return Ok(cluster); + } + cluster += 1; + } + Err(io::Error::new(io::ErrorKind::Other, "No space left on device")) + } + + fn count_free(fat: &mut T, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?; + while cluster < end_cluster { + let val = fat.read_u16::()?; + if val == 0 { + count += 1; + } + cluster += 1; + } + Ok(count) + } +} + +impl FatTrait for Fat32 { + fn get_raw(fat: &mut T, cluster: u32) -> io::Result { + fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?; + Ok(fat.read_u32::()?) + } + + fn get(fat: &mut T, cluster: u32) -> io::Result { + let val = Self::get_raw(fat, cluster)? & 0x0FFFFFFF; + Ok(match val { + 0 if cluster >= 0x0FFFFFF7 && cluster <= 0x0FFFFFFF => { + let tmp = if cluster == 0x0FFFFFF7 { "BAD_CLUSTER" } else { "end-of-chain" }; + warn!( + "cluster number {} is a special value in FAT to indicate {}; it should never be seen as free", + cluster, tmp + ); + FatValue::Bad // avoid accidental use or allocation into a FAT chain + }, + 0 => FatValue::Free, + 0x0FFFFFF7 => FatValue::Bad, + 0x0FFFFFF8...0x0FFFFFFF => FatValue::EndOfChain, + n if cluster >= 0x0FFFFFF7 && cluster <= 0x0FFFFFFF => { + let tmp = if cluster == 0x0FFFFFF7 { "BAD_CLUSTER" } else { "end-of-chain" }; + warn!("cluster number {} is a special value in FAT to indicate {}; hiding potential FAT chain value {} and instead reporting as a bad sector", cluster, tmp, n); + FatValue::Bad // avoid accidental use or allocation into a FAT chain + }, + n => FatValue::Data(n as u32), + }) + } + + fn set_raw(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()> { + fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?; + fat.write_u32::(raw_value)?; + Ok(()) + } + + fn set(fat: &mut T, cluster: u32, value: FatValue) -> io::Result<()> { + let old_reserved_bits = Self::get_raw(fat, cluster)? & 0xF0000000; + + if value == FatValue::Free && cluster >= 0x0FFFFFF7 && cluster <= 0x0FFFFFFF { + // NOTE: it is technically allowed for them to store FAT chain loops, + // or even have them all store value '4' as their next cluster. + // Some believe only FatValue::Bad should be allowed for this edge case. + let tmp = if cluster == 0x0FFFFFF7 { "BAD_CLUSTER" } else { "end-of-chain" }; + panic!( + "cluster number {} is a special value in FAT to indicate {}; it should never be set as free", + cluster, tmp + ); + }; + let raw_val = match value { + FatValue::Free => 0, + FatValue::Bad => 0x0FFFFFF7, + FatValue::EndOfChain => 0x0FFFFFFF, + FatValue::Data(n) => n, + }; + let raw_val = raw_val | old_reserved_bits; // must preserve original reserved values + Self::set_raw(fat, cluster, raw_val) + } + + fn find_free(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result { + let mut cluster = start_cluster; + fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?; + while cluster < end_cluster { + let val = fat.read_u32::()? & 0x0FFFFFFF; + if val == 0 { + return Ok(cluster); + } + cluster += 1; + } + Err(io::Error::new(io::ErrorKind::Other, "No space left on device")) + } + + fn count_free(fat: &mut T, end_cluster: u32) -> io::Result { + let mut count = 0; + let mut cluster = RESERVED_FAT_ENTRIES; + fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?; + while cluster < end_cluster { + let val = fat.read_u32::()? & 0x0FFFFFFF; + if val == 0 { + count += 1; + } + cluster += 1; + } + Ok(count) + } +} + +pub(crate) struct ClusterIterator { + fat: T, + fat_type: FatType, + cluster: Option, + err: bool, +} + +impl ClusterIterator { + pub(crate) fn new(fat: T, fat_type: FatType, cluster: u32) -> Self { + ClusterIterator { fat, fat_type, cluster: Some(cluster), err: false } + } + + pub(crate) fn truncate(&mut self) -> io::Result { + match self.cluster { + Some(n) => { + // Move to the next cluster + self.next(); + // Mark previous cluster as end of chain + write_fat(&mut self.fat, self.fat_type, n, FatValue::EndOfChain)?; + // Free rest of chain + self.free() + }, + None => Ok(0), + } + } + + pub(crate) fn free(&mut self) -> io::Result { + let mut num_free = 0; + while let Some(n) = self.cluster { + self.next(); + write_fat(&mut self.fat, self.fat_type, n, FatValue::Free)?; + num_free += 1; + } + Ok(num_free) + } +} + +impl Iterator for ClusterIterator { + type Item = io::Result; + + fn next(&mut self) -> Option { + if self.err { + return None; + } + if let Some(current_cluster) = self.cluster { + self.cluster = match get_next_cluster(&mut self.fat, self.fat_type, current_cluster) { + Ok(next_cluster) => next_cluster, + Err(err) => { + self.err = true; + return Some(Err(err)); + }, + } + } + self.cluster.map(Ok) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn test_fat(fat_type: FatType, mut cur: T) { + // based on cluster maps from Wikipedia: + // https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system#Cluster_map + assert_eq!(read_fat(&mut cur, fat_type, 1).unwrap(), FatValue::EndOfChain); + assert_eq!(read_fat(&mut cur, fat_type, 4).unwrap(), FatValue::Data(5)); + assert_eq!(read_fat(&mut cur, fat_type, 5).unwrap(), FatValue::Data(6)); + assert_eq!(read_fat(&mut cur, fat_type, 8).unwrap(), FatValue::EndOfChain); + assert_eq!(read_fat(&mut cur, fat_type, 9).unwrap(), FatValue::Data(0xA)); + assert_eq!(read_fat(&mut cur, fat_type, 0xA).unwrap(), FatValue::Data(0x14)); + assert_eq!(read_fat(&mut cur, fat_type, 0x12).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0x17).unwrap(), FatValue::Bad); + assert_eq!(read_fat(&mut cur, fat_type, 0x18).unwrap(), FatValue::Bad); + assert_eq!(read_fat(&mut cur, fat_type, 0x1B).unwrap(), FatValue::Free); + + assert_eq!(find_free_cluster(&mut cur, fat_type, 2, 0x20).unwrap(), 0x12); + assert_eq!(find_free_cluster(&mut cur, fat_type, 0x12, 0x20).unwrap(), 0x12); + assert_eq!(find_free_cluster(&mut cur, fat_type, 0x13, 0x20).unwrap(), 0x1B); + assert!(find_free_cluster(&mut cur, fat_type, 0x13, 0x14).is_err()); + + assert_eq!(count_free_clusters(&mut cur, fat_type, 0x1E).unwrap(), 5); + + // test allocation + assert_eq!(alloc_cluster(&mut cur, fat_type, None, Some(0x13), 0x1E).unwrap(), 0x1B); + assert_eq!(read_fat(&mut cur, fat_type, 0x1B).unwrap(), FatValue::EndOfChain); + assert_eq!(alloc_cluster(&mut cur, fat_type, Some(0x1B), None, 0x1E).unwrap(), 0x12); + assert_eq!(read_fat(&mut cur, fat_type, 0x1B).unwrap(), FatValue::Data(0x12)); + assert_eq!(read_fat(&mut cur, fat_type, 0x12).unwrap(), FatValue::EndOfChain); + assert_eq!(count_free_clusters(&mut cur, fat_type, 0x1E).unwrap(), 3); + // test reading from iterator + { + let iter = ClusterIterator::new(&mut cur, fat_type, 0x9); + assert_eq!(iter.map(|r| r.unwrap()).collect::>(), vec![0xA, 0x14, 0x15, 0x16, 0x19, 0x1A]); + } + // test truncating a chain + { + let mut iter = ClusterIterator::new(&mut cur, fat_type, 0x9); + assert_eq!(iter.nth(3).unwrap().unwrap(), 0x16); + iter.truncate().unwrap(); + } + assert_eq!(read_fat(&mut cur, fat_type, 0x16).unwrap(), FatValue::EndOfChain); + assert_eq!(read_fat(&mut cur, fat_type, 0x19).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0x1A).unwrap(), FatValue::Free); + // test freeing a chain + { + let mut iter = ClusterIterator::new(&mut cur, fat_type, 0x9); + iter.free().unwrap(); + } + assert_eq!(read_fat(&mut cur, fat_type, 0x9).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0xA).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0x14).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0x15).unwrap(), FatValue::Free); + assert_eq!(read_fat(&mut cur, fat_type, 0x16).unwrap(), FatValue::Free); + } + + #[test] + fn test_fat12() { + let fat: Vec = vec![ + 0xF0, 0xFF, 0xFF, 0x03, 0x40, 0x00, 0x05, 0x60, 0x00, 0x07, 0x80, 0x00, 0xFF, 0xAF, 0x00, 0x14, 0xC0, 0x00, + 0x0D, 0xE0, 0x00, 0x0F, 0x00, 0x01, 0x11, 0xF0, 0xFF, 0x00, 0xF0, 0xFF, 0x15, 0x60, 0x01, 0x19, 0x70, 0xFF, + 0xF7, 0xAF, 0x01, 0xFF, 0x0F, 0x00, 0x00, 0x70, 0xFF, 0x00, 0x00, 0x00, + ]; + test_fat(FatType::Fat12, io::Cursor::new(fat)); + } + + #[test] + fn test_fat16() { + let fat: Vec = vec![ + 0xF0, 0xFF, 0xFF, 0xFF, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00, 0x08, 0x00, 0xFF, 0xFF, + 0x0A, 0x00, 0x14, 0x00, 0x0C, 0x00, 0x0D, 0x00, 0x0E, 0x00, 0x0F, 0x00, 0x10, 0x00, 0x11, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0xFF, 0xFF, 0x15, 0x00, 0x16, 0x00, 0x19, 0x00, 0xF7, 0xFF, 0xF7, 0xFF, 0x1A, 0x00, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0xF7, 0xFF, 0x00, 0x00, 0x00, 0x00, + ]; + test_fat(FatType::Fat16, io::Cursor::new(fat)); + } + + #[test] + fn test_fat32() { + let fat: Vec = vec![ + 0xF0, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0xFF, 0xFF, 0xFF, 0x0F, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, + 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F, + 0x0A, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F, 0x15, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x19, 0x00, + 0x00, 0x00, 0xF7, 0xFF, 0xFF, 0x0F, 0xF7, 0xFF, 0xFF, 0x0F, 0x1A, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x0F, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, + ]; + test_fat(FatType::Fat32, io::Cursor::new(fat)); + } +} diff --git a/src/time.rs b/src/time.rs new file mode 100644 index 0000000..69fec60 --- /dev/null +++ b/src/time.rs @@ -0,0 +1,201 @@ +use core::fmt::Debug; + +#[cfg(feature = "chrono")] +use chrono; +#[cfg(feature = "chrono")] +use chrono::{Datelike, Local, TimeZone, Timelike}; + +/// A DOS compatible date. +/// +/// Used by `DirEntry` time-related methods. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Date { + /// Full year - [1980, 2107] + pub year: u16, + /// Month of the year - [1, 12] + pub month: u16, + /// Day of the month - [1, 31] + pub day: u16, +} + +impl Date { + pub(crate) fn decode(dos_date: u16) -> Self { + let (year, month, day) = ((dos_date >> 9) + 1980, (dos_date >> 5) & 0xF, dos_date & 0x1F); + Date { year, month, day } + } + + pub(crate) fn encode(&self) -> u16 { + ((self.year - 1980) << 9) | (self.month << 5) | self.day + } +} + +/// A DOS compatible time. +/// +/// Used by `DirEntry` time-related methods. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct Time { + /// Hours after midnight - [0, 23] + pub hour: u16, + /// Minutes after the hour - [0, 59] + pub min: u16, + /// Seconds after the minute - [0, 59] + pub sec: u16, + /// Milliseconds after the second - [0, 999] + pub millis: u16, +} + +impl Time { + pub(crate) fn decode(dos_time: u16, dos_time_hi_res: u8) -> Self { + let hour = dos_time >> 11; + let min = (dos_time >> 5) & 0x3F; + let sec = (dos_time & 0x1F) * 2 + (dos_time_hi_res as u16) / 100; + let millis = (dos_time_hi_res as u16 % 100) * 10; + Time { hour, min, sec, millis } + } + + pub(crate) fn encode(&self) -> (u16, u8) { + let dos_time = (self.hour << 11) | (self.min << 5) | (self.sec / 2); + let dos_time_hi_res = ((self.millis / 10) + (self.sec % 2) * 100) as u8; + (dos_time, dos_time_hi_res) + } +} + +/// A DOS compatible date and time. +/// +/// Used by `DirEntry` time-related methods. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub struct DateTime { + /// A date part + pub date: Date, + // A time part + pub time: Time, +} + +impl DateTime { + pub(crate) fn decode(dos_date: u16, dos_time: u16, dos_time_hi_res: u8) -> Self { + DateTime { date: Date::decode(dos_date), time: Time::decode(dos_time, dos_time_hi_res) } + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::Date { + fn from(date: Date) -> Self { + Local.ymd(date.year as i32, date.month as u32, date.day as u32) + } +} + +#[cfg(feature = "chrono")] +impl From for chrono::DateTime { + fn from(date_time: DateTime) -> Self { + chrono::Date::::from(date_time.date).and_hms_milli( + date_time.time.hour as u32, + date_time.time.min as u32, + date_time.time.sec as u32, + date_time.time.millis as u32, + ) + } +} + +#[cfg(feature = "chrono")] +impl From> for Date { + fn from(date: chrono::Date) -> Self { + Date { year: date.year() as u16, month: date.month() as u16, day: date.day() as u16 } + } +} + +#[cfg(feature = "chrono")] +impl From> for DateTime { + fn from(date_time: chrono::DateTime) -> Self { + DateTime { + date: Date::from(date_time.date()), + time: Time { + hour: date_time.hour() as u16, + min: date_time.minute() as u16, + sec: date_time.second() as u16, + millis: (date_time.nanosecond() / 1_000_000) as u16, + }, + } + } +} + +/// A current time and date provider. +/// +/// Provides a custom implementation for a time resolution used when updating directory entry time fields. +/// Default implementation gets time from `chrono` crate if `chrono` feature is enabled. +/// Otherwise default implementation returns DOS minimal date-time (1980/1/1 0:00:00). +/// `TimeProvider` is specified by the `time_provider` property in `FsOptions` struct. +pub trait TimeProvider: Debug { + fn get_current_date(&self) -> Date; + fn get_current_date_time(&self) -> DateTime; +} + +#[derive(Debug)] +pub(crate) struct DefaultTimeProvider { + _dummy: (), +} + +impl TimeProvider for DefaultTimeProvider { + #[cfg(feature = "chrono")] + fn get_current_date(&self) -> Date { + Date::from(chrono::Local::now().date()) + } + #[cfg(not(feature = "chrono"))] + fn get_current_date(&self) -> Date { + Date::decode(0) + } + + #[cfg(feature = "chrono")] + fn get_current_date_time(&self) -> DateTime { + DateTime::from(chrono::Local::now()) + } + #[cfg(not(feature = "chrono"))] + fn get_current_date_time(&self) -> DateTime { + DateTime::decode(0, 0, 0) + } +} + +pub(crate) static DEFAULT_TIME_PROVIDER: DefaultTimeProvider = DefaultTimeProvider { _dummy: () }; + +#[cfg(test)] +mod tests { + use super::{Date, Time}; + + #[test] + fn date_encode_decode() { + let d = Date { + year: 2055, + month: 7, + day: 23 + }; + let x = d.encode(); + assert_eq!(x, 38647); + assert_eq!(d, Date::decode(x)); + } + + #[test] + fn time_encode_decode() { + let t1 = Time { + hour: 15, + min: 3, + sec: 29, + millis: 990, + }; + let t2 = Time { + sec: 18, + .. t1 + }; + let t3 = Time { + millis: 40, + .. t1 + }; + let (x1, y1) = t1.encode(); + let (x2, y2) = t2.encode(); + let (x3, y3) = t3.encode(); + assert_eq!((x1, y1), (30830, 199)); + assert_eq!((x2, y2), (30825, 99)); + assert_eq!((x3, y3), (30830, 104)); + assert_eq!(t1, Time::decode(x1, y1)); + assert_eq!(t2, Time::decode(x2, y2)); + assert_eq!(t3, Time::decode(x3, y3)); + } +} diff --git a/tests/format.rs b/tests/format.rs new file mode 100644 index 0000000..b596532 --- /dev/null +++ b/tests/format.rs @@ -0,0 +1,110 @@ +extern crate env_logger; +extern crate fatfs; +extern crate fscommon; + +use std::io; +use std::io::prelude::*; + +use fscommon::BufStream; + +const KB: u64 = 1024; +const MB: u64 = KB * 1024; +const TEST_STR: &str = "Hi there Rust programmer!\n"; + +type FileSystem = fatfs::FileSystem>>>; + +fn basic_fs_test(fs: &FileSystem) { + let stats = fs.stats().expect("stats"); + if fs.fat_type() == fatfs::FatType::Fat32 { + // On FAT32 one cluster is allocated for root directory + assert_eq!(stats.total_clusters(), stats.free_clusters() + 1); + } else { + assert_eq!(stats.total_clusters(), stats.free_clusters()); + } + + let root_dir = fs.root_dir(); + let entries = root_dir.iter().map(|r| r.unwrap()).collect::>(); + assert_eq!(entries.len(), 0); + + let subdir1 = root_dir.create_dir("subdir1").expect("create_dir subdir1"); + let subdir2 = root_dir.create_dir("subdir1/subdir2 with long name").expect("create_dir subdir2"); + + let test_str = TEST_STR.repeat(1000); + { + let mut file = subdir2.create_file("test file name.txt").expect("create file"); + file.truncate().expect("truncate file"); + file.write_all(test_str.as_bytes()).expect("write file"); + } + + let mut file = root_dir.open_file("subdir1/subdir2 with long name/test file name.txt").unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).expect("read_to_string"); + assert_eq!(content, test_str); + + let filenames = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(filenames, ["subdir1"]); + + let filenames = subdir2.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(filenames, [".", "..", "test file name.txt"]); + + subdir1.rename("subdir2 with long name/test file name.txt", &root_dir, "new-name.txt").expect("rename"); + + let filenames = subdir2.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(filenames, [".", ".."]); + + let filenames = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(filenames, ["subdir1", "new-name.txt"]); +} + +fn test_format_fs(opts: fatfs::FormatVolumeOptions, total_bytes: u64) -> FileSystem { + let _ = env_logger::try_init(); + // Init storage to 0xD1 bytes (value has been choosen to be parsed as normal file) + let storage_vec: Vec = vec![0xD1u8; total_bytes as usize]; + let storage_cur = io::Cursor::new(storage_vec); + let mut buffered_stream = BufStream::new(storage_cur); + fatfs::format_volume(&mut buffered_stream, opts).expect("format volume"); + + let fs = fatfs::FileSystem::new(buffered_stream, fatfs::FsOptions::new()).expect("open fs"); + basic_fs_test(&fs); + fs +} + +#[test] +fn test_format_1mb() { + let total_bytes = 1 * MB; + let opts = fatfs::FormatVolumeOptions::new(); + let fs = test_format_fs(opts, total_bytes); + assert_eq!(fs.fat_type(), fatfs::FatType::Fat12); +} + +#[test] +fn test_format_8mb_1fat() { + let total_bytes = 8 * MB; + let opts = fatfs::FormatVolumeOptions::new().fats(1); + let fs = test_format_fs(opts, total_bytes); + assert_eq!(fs.fat_type(), fatfs::FatType::Fat16); +} + +#[test] +fn test_format_50mb() { + let total_bytes = 50 * MB; + let opts = fatfs::FormatVolumeOptions::new(); + let fs = test_format_fs(opts, total_bytes); + assert_eq!(fs.fat_type(), fatfs::FatType::Fat16); +} + +#[test] +fn test_format_512mb_512sec() { + let total_bytes = 2 * 1024 * MB; + let opts = fatfs::FormatVolumeOptions::new(); + let fs = test_format_fs(opts, total_bytes); + assert_eq!(fs.fat_type(), fatfs::FatType::Fat32); +} + +#[test] +fn test_format_512mb_4096sec() { + let total_bytes = 1 * 1024 * MB; + let opts = fatfs::FormatVolumeOptions::new().bytes_per_sector(4096); + let fs = test_format_fs(opts, total_bytes); + assert_eq!(fs.fat_type(), fatfs::FatType::Fat32); +} diff --git a/tests/read.rs b/tests/read.rs new file mode 100644 index 0000000..c8494de --- /dev/null +++ b/tests/read.rs @@ -0,0 +1,267 @@ +extern crate env_logger; +extern crate fatfs; +extern crate fscommon; + +use std::fs; +use std::io::prelude::*; +use std::io::SeekFrom; +use std::str; + +use fatfs::{FatType, FsOptions}; +use fscommon::BufStream; + +const TEST_TEXT: &str = "Rust is cool!\n"; +const FAT12_IMG: &str = "resources/fat12.img"; +const FAT16_IMG: &str = "resources/fat16.img"; +const FAT32_IMG: &str = "resources/fat32.img"; + +type FileSystem = fatfs::FileSystem>; + +fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str) { + let _ = env_logger::try_init(); + let file = fs::File::open(filename).unwrap(); + let buf_file = BufStream::new(file); + let fs = FileSystem::new(buf_file, FsOptions::new()).unwrap(); + f(fs); +} + +fn test_root_dir(fs: FileSystem) { + let root_dir = fs.root_dir(); + let entries = root_dir.iter().map(|r| r.unwrap()).collect::>(); + let short_names = entries.iter().map(|e| e.short_file_name()).collect::>(); + assert_eq!(short_names, ["LONG.TXT", "SHORT.TXT", "VERY", "VERY-L~1"]); + let names = entries.iter().map(|e| e.file_name()).collect::>(); + assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name"]); + // Try read again + let names2 = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names2, names); +} + +#[test] +fn test_root_dir_fat12() { + call_with_fs(&test_root_dir, FAT12_IMG) +} + +#[test] +fn test_root_dir_fat16() { + call_with_fs(&test_root_dir, FAT16_IMG) +} + +#[test] +fn test_root_dir_fat32() { + call_with_fs(&test_root_dir, FAT32_IMG) +} + +fn test_read_seek_short_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let mut short_file = root_dir.open_file("short.txt").unwrap(); + let mut buf = Vec::new(); + short_file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); + + assert_eq!(short_file.seek(SeekFrom::Start(5)).unwrap(), 5); + let mut buf2 = [0; 5]; + short_file.read_exact(&mut buf2).unwrap(); + assert_eq!(str::from_utf8(&buf2).unwrap(), &TEST_TEXT[5..10]); + + assert_eq!(short_file.seek(SeekFrom::Start(1000)).unwrap(), TEST_TEXT.len() as u64); + let mut buf2 = [0; 5]; + assert_eq!(short_file.read(&mut buf2).unwrap(), 0); +} + +#[test] +fn test_read_seek_short_file_fat12() { + call_with_fs(&test_read_seek_short_file, FAT12_IMG) +} + +#[test] +fn test_read_seek_short_file_fat16() { + call_with_fs(&test_read_seek_short_file, FAT16_IMG) +} + +#[test] +fn test_read_seek_short_file_fat32() { + call_with_fs(&test_read_seek_short_file, FAT32_IMG) +} + +fn test_read_long_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let mut long_file = root_dir.open_file("long.txt").unwrap(); + let mut buf = Vec::new(); + long_file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT.repeat(1000)); + + assert_eq!(long_file.seek(SeekFrom::Start(2017)).unwrap(), 2017); + buf.clear(); + let mut buf2 = [0; 10]; + long_file.read_exact(&mut buf2).unwrap(); + assert_eq!(str::from_utf8(&buf2).unwrap(), &TEST_TEXT.repeat(1000)[2017..2027]); +} + +#[test] +fn test_read_long_file_fat12() { + call_with_fs(&test_read_long_file, FAT12_IMG) +} + +#[test] +fn test_read_long_file_fat16() { + call_with_fs(&test_read_long_file, FAT16_IMG) +} + +#[test] +fn test_read_long_file_fat32() { + call_with_fs(&test_read_long_file, FAT32_IMG) +} + +fn test_get_dir_by_path(fs: FileSystem) { + let root_dir = fs.root_dir(); + let dir = root_dir.open_dir("very/long/path/").unwrap(); + let names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + + let dir2 = root_dir.open_dir("very/long/path/././.").unwrap(); + let names2 = dir2.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names2, [".", "..", "test.txt"]); + + let root_dir2 = root_dir.open_dir("very/long/path/../../..").unwrap(); + let root_names = root_dir2.iter().map(|r| r.unwrap().file_name()).collect::>(); + let root_names2 = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(root_names, root_names2); + + root_dir.open_dir("VERY-L~1").unwrap(); +} + +#[test] +fn test_get_dir_by_path_fat12() { + call_with_fs(&test_get_dir_by_path, FAT12_IMG) +} + +#[test] +fn test_get_dir_by_path_fat16() { + call_with_fs(&test_get_dir_by_path, FAT16_IMG) +} + +#[test] +fn test_get_dir_by_path_fat32() { + call_with_fs(&test_get_dir_by_path, FAT32_IMG) +} + +fn test_get_file_by_path(fs: FileSystem) { + let root_dir = fs.root_dir(); + let mut file = root_dir.open_file("very/long/path/test.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); + + let mut file = root_dir.open_file("very-long-dir-name/very-long-file-name.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_TEXT); + + root_dir.open_file("VERY-L~1/VERY-L~1.TXT").unwrap(); + + // try opening dir as file + assert!(root_dir.open_file("very/long/path").is_err()); + // try opening file as dir + assert!(root_dir.open_dir("very/long/path/test.txt").is_err()); + // try using invalid path containing file as non-last component + assert!(root_dir.open_file("very/long/path/test.txt/abc").is_err()); + assert!(root_dir.open_dir("very/long/path/test.txt/abc").is_err()); +} + +#[test] +fn test_get_file_by_path_fat12() { + call_with_fs(&test_get_file_by_path, FAT12_IMG) +} + +#[test] +fn test_get_file_by_path_fat16() { + call_with_fs(&test_get_file_by_path, FAT16_IMG) +} + +#[test] +fn test_get_file_by_path_fat32() { + call_with_fs(&test_get_file_by_path, FAT32_IMG) +} + +fn test_volume_metadata(fs: FileSystem, fat_type: FatType) { + assert_eq!(fs.volume_id(), 0x12345678); + assert_eq!(fs.volume_label(), "Test!"); + assert_eq!(&fs.read_volume_label_from_root_dir().unwrap().unwrap(), "Test!"); + assert_eq!(fs.fat_type(), fat_type); +} + +#[test] +fn test_volume_metadata_fat12() { + call_with_fs(&|fs| test_volume_metadata(fs, FatType::Fat12), FAT12_IMG) +} + +#[test] +fn test_volume_metadata_fat16() { + call_with_fs(&|fs| test_volume_metadata(fs, FatType::Fat16), FAT16_IMG) +} + +#[test] +fn test_volume_metadata_fat32() { + call_with_fs(&|fs| test_volume_metadata(fs, FatType::Fat32), FAT32_IMG) +} + +fn test_status_flags(fs: FileSystem) { + let status_flags = fs.read_status_flags().unwrap(); + assert_eq!(status_flags.dirty(), false); + assert_eq!(status_flags.io_error(), false); +} + +#[test] +fn test_status_flags_fat12() { + call_with_fs(&|fs| test_status_flags(fs), FAT12_IMG) +} + +#[test] +fn test_status_flags_fat16() { + call_with_fs(&|fs| test_status_flags(fs), FAT16_IMG) +} + +#[test] +fn test_status_flags_fat32() { + call_with_fs(&|fs| test_status_flags(fs), FAT32_IMG) +} + +#[test] +fn test_stats_fat12() { + call_with_fs( + &|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size(), 512); + assert_eq!(stats.total_clusters(), 1955); // 1000 * 1024 / 512 = 2000 + assert_eq!(stats.free_clusters(), 1920); + }, + FAT12_IMG, + ) +} + +#[test] +fn test_stats_fat16() { + call_with_fs( + &|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size(), 512); + assert_eq!(stats.total_clusters(), 4927); // 2500 * 1024 / 512 = 5000 + assert_eq!(stats.free_clusters(), 4892); + }, + FAT16_IMG, + ) +} + +#[test] +fn test_stats_fat32() { + call_with_fs( + &|fs| { + let stats = fs.stats().unwrap(); + assert_eq!(stats.cluster_size(), 512); + assert_eq!(stats.total_clusters(), 66922); // 34000 * 1024 / 512 = 68000 + assert_eq!(stats.free_clusters(), 66886); + }, + FAT32_IMG, + ) +} diff --git a/tests/write.rs b/tests/write.rs new file mode 100644 index 0000000..f4a09bb --- /dev/null +++ b/tests/write.rs @@ -0,0 +1,384 @@ +extern crate env_logger; +extern crate fatfs; +extern crate fscommon; + +use std::fs; +use std::io; +use std::io::prelude::*; +use std::mem; +use std::str; + +use fatfs::FsOptions; +use fscommon::BufStream; + +const FAT12_IMG: &str = "fat12.img"; +const FAT16_IMG: &str = "fat16.img"; +const FAT32_IMG: &str = "fat32.img"; +const IMG_DIR: &str = "resources"; +const TMP_DIR: &str = "tmp"; +const TEST_STR: &str = "Hi there Rust programmer!\n"; +const TEST_STR2: &str = "Rust is cool!\n"; + +type FileSystem = fatfs::FileSystem>; + +fn call_with_tmp_img(f: &Fn(&str) -> (), filename: &str, test_seq: u32) { + let _ = env_logger::try_init(); + let img_path = format!("{}/{}", IMG_DIR, filename); + let tmp_path = format!("{}/{}-{}", TMP_DIR, test_seq, filename); + fs::create_dir(TMP_DIR).ok(); + fs::copy(&img_path, &tmp_path).unwrap(); + f(tmp_path.as_str()); + fs::remove_file(tmp_path).unwrap(); +} + +fn open_filesystem_rw(tmp_path: &str) -> FileSystem { + let file = fs::OpenOptions::new().read(true).write(true).open(&tmp_path).unwrap(); + let buf_file = BufStream::new(file); + let options = FsOptions::new().update_accessed_date(true); + FileSystem::new(buf_file, options).unwrap() +} + +fn call_with_fs(f: &Fn(FileSystem) -> (), filename: &str, test_seq: u32) { + let callback = |tmp_path: &str| { + let fs = open_filesystem_rw(tmp_path); + f(fs); + }; + call_with_tmp_img(&callback, filename, test_seq); +} + +fn test_write_short_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let mut file = root_dir.open_file("short.txt").expect("open file"); + file.truncate().unwrap(); + file.write_all(&TEST_STR.as_bytes()).unwrap(); + file.seek(io::SeekFrom::Start(0)).unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(TEST_STR, str::from_utf8(&buf).unwrap()); +} + +#[test] +fn test_write_file_fat12() { + call_with_fs(&test_write_short_file, FAT12_IMG, 1) +} + +#[test] +fn test_write_file_fat16() { + call_with_fs(&test_write_short_file, FAT16_IMG, 1) +} + +#[test] +fn test_write_file_fat32() { + call_with_fs(&test_write_short_file, FAT32_IMG, 1) +} + +fn test_write_long_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let mut file = root_dir.open_file("long.txt").expect("open file"); + file.truncate().unwrap(); + let test_str = TEST_STR.repeat(1000); + file.write_all(&test_str.as_bytes()).unwrap(); + file.seek(io::SeekFrom::Start(0)).unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(test_str, str::from_utf8(&buf).unwrap()); + file.seek(io::SeekFrom::Start(1234)).unwrap(); + file.truncate().unwrap(); + file.seek(io::SeekFrom::Start(0)).unwrap(); + buf.clear(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(&test_str[..1234], str::from_utf8(&buf).unwrap()); +} + +#[test] +fn test_write_long_file_fat12() { + call_with_fs(&test_write_long_file, FAT12_IMG, 2) +} + +#[test] +fn test_write_long_file_fat16() { + call_with_fs(&test_write_long_file, FAT16_IMG, 2) +} + +#[test] +fn test_write_long_file_fat32() { + call_with_fs(&test_write_long_file, FAT32_IMG, 2) +} + +fn test_remove(fs: FileSystem) { + let root_dir = fs.root_dir(); + assert!(root_dir.remove("very/long/path").is_err()); + let dir = root_dir.open_dir("very/long/path").unwrap(); + let mut names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + root_dir.remove("very/long/path/test.txt").unwrap(); + names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", ".."]); + assert!(root_dir.remove("very/long/path").is_ok()); + + names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name"]); + root_dir.remove("long.txt").unwrap(); + names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, ["short.txt", "very", "very-long-dir-name"]); +} + +#[test] +fn test_remove_fat12() { + call_with_fs(&test_remove, FAT12_IMG, 3) +} + +#[test] +fn test_remove_fat16() { + call_with_fs(&test_remove, FAT16_IMG, 3) +} + +#[test] +fn test_remove_fat32() { + call_with_fs(&test_remove, FAT32_IMG, 3) +} + +fn test_create_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let dir = root_dir.open_dir("very/long/path").unwrap(); + let mut names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + { + // test some invalid names + assert!(root_dir.create_file("very/long/path/:").is_err()); + assert!(root_dir.create_file("very/long/path/\0").is_err()); + // create file + let mut file = root_dir.create_file("very/long/path/new-file-with-long-name.txt").unwrap(); + file.write_all(&TEST_STR.as_bytes()).unwrap(); + } + // check for dir entry + names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt", "new-file-with-long-name.txt"]); + names = dir.iter().map(|r| r.unwrap().short_file_name()).collect::>(); + assert_eq!(names, [".", "..", "TEST.TXT", "NEW-FI~1.TXT"]); + { + // check contents + let mut file = root_dir.open_file("very/long/path/new-file-with-long-name.txt").unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + assert_eq!(&content, &TEST_STR); + } + // Create enough entries to allocate next cluster + for i in 0..512 / 32 { + let name = format!("test{}", i); + dir.create_file(&name).unwrap(); + } + names = dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names.len(), 4 + 512 / 32); + // check creating existing file opens it + { + let mut file = root_dir.create_file("very/long/path/new-file-with-long-name.txt").unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + assert_eq!(&content, &TEST_STR); + } + // check using create_file with existing directory fails + assert!(root_dir.create_file("very").is_err()); +} + +#[test] +fn test_create_file_fat12() { + call_with_fs(&test_create_file, FAT12_IMG, 4) +} + +#[test] +fn test_create_file_fat16() { + call_with_fs(&test_create_file, FAT16_IMG, 4) +} + +#[test] +fn test_create_file_fat32() { + call_with_fs(&test_create_file, FAT32_IMG, 4) +} + +fn test_create_dir(fs: FileSystem) { + let root_dir = fs.root_dir(); + let parent_dir = root_dir.open_dir("very/long/path").unwrap(); + let mut names = parent_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + { + let subdir = root_dir.create_dir("very/long/path/new-dir-with-long-name").unwrap(); + names = subdir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", ".."]); + } + // check if new entry is visible in parent + names = parent_dir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt", "new-dir-with-long-name"]); + { + // Check if new directory can be opened and read + let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name").unwrap(); + names = subdir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", ".."]); + } + // Check if '.' is alias for new directory + { + let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name/.").unwrap(); + names = subdir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", ".."]); + } + // Check if '..' is alias for parent directory + { + let subdir = root_dir.open_dir("very/long/path/new-dir-with-long-name/..").unwrap(); + names = subdir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt", "new-dir-with-long-name"]); + } + // check if creating existing directory returns it + { + let subdir = root_dir.create_dir("very").unwrap(); + names = subdir.iter().map(|r| r.unwrap().file_name()).collect::>(); + assert_eq!(names, [".", "..", "long"]); + } + // check short names validity after create_dir + { + let subdir = root_dir.create_dir("test").unwrap(); + names = subdir.iter().map(|r| r.unwrap().short_file_name()).collect::>(); + assert_eq!(names, [".", ".."]); + } + + // check using create_dir with existing file fails + assert!(root_dir.create_dir("very/long/path/test.txt").is_err()); +} + +#[test] +fn test_create_dir_fat12() { + call_with_fs(&test_create_dir, FAT12_IMG, 5) +} + +#[test] +fn test_create_dir_fat16() { + call_with_fs(&test_create_dir, FAT16_IMG, 5) +} + +#[test] +fn test_create_dir_fat32() { + call_with_fs(&test_create_dir, FAT32_IMG, 5) +} + +fn test_rename_file(fs: FileSystem) { + let root_dir = fs.root_dir(); + let parent_dir = root_dir.open_dir("very/long/path").unwrap(); + let entries = parent_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, [".", "..", "test.txt"]); + assert_eq!(entries[2].len(), 14); + let stats = fs.stats().unwrap(); + + parent_dir.rename("test.txt", &parent_dir, "new-long-name.txt").unwrap(); + let entries = parent_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, [".", "..", "new-long-name.txt"]); + assert_eq!(entries[2].len(), TEST_STR2.len() as u64); + let mut file = parent_dir.open_file("new-long-name.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2); + + parent_dir.rename("new-long-name.txt", &root_dir, "moved-file.txt").unwrap(); + let entries = root_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name", "moved-file.txt"]); + assert_eq!(entries[4].len(), TEST_STR2.len() as u64); + let mut file = root_dir.open_file("moved-file.txt").unwrap(); + let mut buf = Vec::new(); + file.read_to_end(&mut buf).unwrap(); + assert_eq!(str::from_utf8(&buf).unwrap(), TEST_STR2); + + assert!(root_dir.rename("moved-file.txt", &root_dir, "short.txt").is_err()); + let entries = root_dir.iter().map(|r| r.unwrap()).collect::>(); + let names = entries.iter().map(|r| r.file_name()).collect::>(); + assert_eq!(names, ["long.txt", "short.txt", "very", "very-long-dir-name", "moved-file.txt"]); + + assert!(root_dir.rename("moved-file.txt", &root_dir, "moved-file.txt").is_ok()); + + let new_stats = fs.stats().unwrap(); + assert_eq!(new_stats.free_clusters(), stats.free_clusters()); +} + +#[test] +fn test_rename_file_fat12() { + call_with_fs(&test_rename_file, FAT12_IMG, 6) +} + +#[test] +fn test_rename_file_fat16() { + call_with_fs(&test_rename_file, FAT16_IMG, 6) +} + +#[test] +fn test_rename_file_fat32() { + call_with_fs(&test_rename_file, FAT32_IMG, 6) +} + +fn test_dirty_flag(tmp_path: &str) { + // Open filesystem, make change, and forget it - should become dirty + let fs = open_filesystem_rw(tmp_path); + let status_flags = fs.read_status_flags().unwrap(); + assert_eq!(status_flags.dirty(), false); + assert_eq!(status_flags.io_error(), false); + fs.root_dir().create_file("abc.txt").unwrap(); + mem::forget(fs); + // Check if volume is dirty now + let fs = open_filesystem_rw(tmp_path); + let status_flags = fs.read_status_flags().unwrap(); + assert_eq!(status_flags.dirty(), true); + assert_eq!(status_flags.io_error(), false); + fs.unmount().unwrap(); + // Make sure remounting does not clear the dirty flag + let fs = open_filesystem_rw(tmp_path); + let status_flags = fs.read_status_flags().unwrap(); + assert_eq!(status_flags.dirty(), true); + assert_eq!(status_flags.io_error(), false); +} + +#[test] +fn test_dirty_flag_fat12() { + call_with_tmp_img(&test_dirty_flag, FAT12_IMG, 7) +} + +#[test] +fn test_dirty_flag_fat16() { + call_with_tmp_img(&test_dirty_flag, FAT16_IMG, 7) +} + +#[test] +fn test_dirty_flag_fat32() { + call_with_tmp_img(&test_dirty_flag, FAT32_IMG, 7) +} + +fn test_multiple_files_in_directory(fs: FileSystem) { + let dir = fs.root_dir().create_dir("/TMP").unwrap(); + for i in 0..8 { + let name = format!("T{}.TXT", i); + let mut file = dir.create_file(&name).unwrap(); + file.write_all(TEST_STR.as_bytes()).unwrap(); + file.flush().unwrap(); + + let file = dir.iter() + .map(|r| r.unwrap()) + .filter(|e| e.file_name() == name) + .next() + .unwrap(); + + assert_eq!(TEST_STR.len() as u64, file.len(), "Wrong file len on iteration {}", i); + } +} + +#[test] +fn test_multiple_files_in_directory_fat12() { + call_with_fs(&test_multiple_files_in_directory, FAT12_IMG, 8) +} + +#[test] +fn test_multiple_files_in_directory_fat16() { + call_with_fs(&test_multiple_files_in_directory, FAT16_IMG, 8) +} + +#[test] +fn test_multiple_files_in_directory_fat32() { + call_with_fs(&test_multiple_files_in_directory, FAT32_IMG, 8) +}