This commit is contained in:
= 2024-07-21 14:20:26 +08:00
commit b2dde724ca
30 changed files with 7364 additions and 0 deletions

5
.cargo_vcs_info.json Normal file
View File

@ -0,0 +1,5 @@
{
"git": {
"sha1": "954c68457fda6f1b9b87393f3130f74da5f548f8"
}
}

14
.editorconfig Normal file
View File

@ -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

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target
tmp
Cargo.lock

35
.travis.yml Normal file
View File

@ -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

61
CHANGELOG.md Normal file
View File

@ -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

52
Cargo.toml Normal file
View File

@ -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ń <rafalh92@outlook.com>"]
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"

37
Cargo.toml.orig Normal file
View File

@ -0,0 +1,37 @@
[package]
name = "fatfs"
version = "0.3.5"
authors = ["Rafał Harabień <rafalh92@outlook.com>"]
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"

19
LICENSE.txt Normal file
View File

@ -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.

73
README.md Normal file
View File

@ -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`.

4
TODO.md Normal file
View File

@ -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

4
build-nostd.sh Executable file
View File

@ -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

21
examples/cat.rs Normal file
View File

@ -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(())
}

44
examples/ls.rs Normal file
View File

@ -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::<Local>::from(e.modified()).format("%Y-%m-%d %H:%M:%S").to_string();
println!("{:4} {} {}", format_file_size(e.len()), modified, e.file_name());
}
Ok(())
}

16
examples/mkfatfs.rs Normal file
View File

@ -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(())
}

25
examples/partition.rs Normal file
View File

@ -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(())
}

24
examples/write.rs Normal file
View File

@ -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(())
}

8
rustfmt.toml Normal file
View File

@ -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"

27
scripts/create-test-img.sh Executable file
View File

@ -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

884
src/boot_sector.rs Normal file
View File

@ -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<T: Read>(rdr: &mut T) -> io::Result<BiosParameterBlock> {
let mut bpb: BiosParameterBlock = Default::default();
bpb.bytes_per_sector = rdr.read_u16::<LittleEndian>()?;
bpb.sectors_per_cluster = rdr.read_u8()?;
bpb.reserved_sectors = rdr.read_u16::<LittleEndian>()?;
bpb.fats = rdr.read_u8()?;
bpb.root_entries = rdr.read_u16::<LittleEndian>()?;
bpb.total_sectors_16 = rdr.read_u16::<LittleEndian>()?;
bpb.media = rdr.read_u8()?;
bpb.sectors_per_fat_16 = rdr.read_u16::<LittleEndian>()?;
bpb.sectors_per_track = rdr.read_u16::<LittleEndian>()?;
bpb.heads = rdr.read_u16::<LittleEndian>()?;
bpb.hidden_sectors = rdr.read_u32::<LittleEndian>()?;
bpb.total_sectors_32 = rdr.read_u32::<LittleEndian>()?;
if bpb.is_fat32() {
bpb.sectors_per_fat_32 = rdr.read_u32::<LittleEndian>()?;
bpb.extended_flags = rdr.read_u16::<LittleEndian>()?;
bpb.fs_version = rdr.read_u16::<LittleEndian>()?;
bpb.root_dir_first_cluster = rdr.read_u32::<LittleEndian>()?;
bpb.fs_info_sector = rdr.read_u16::<LittleEndian>()?;
bpb.backup_boot_sector = rdr.read_u16::<LittleEndian>()?;
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::<LittleEndian>()?;
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::<LittleEndian>()?;
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<T: Write>(&self, mut wrt: T) -> io::Result<()> {
wrt.write_u16::<LittleEndian>(self.bytes_per_sector)?;
wrt.write_u8(self.sectors_per_cluster)?;
wrt.write_u16::<LittleEndian>(self.reserved_sectors)?;
wrt.write_u8(self.fats)?;
wrt.write_u16::<LittleEndian>(self.root_entries)?;
wrt.write_u16::<LittleEndian>(self.total_sectors_16)?;
wrt.write_u8(self.media)?;
wrt.write_u16::<LittleEndian>(self.sectors_per_fat_16)?;
wrt.write_u16::<LittleEndian>(self.sectors_per_track)?;
wrt.write_u16::<LittleEndian>(self.heads)?;
wrt.write_u32::<LittleEndian>(self.hidden_sectors)?;
wrt.write_u32::<LittleEndian>(self.total_sectors_32)?;
if self.is_fat32() {
wrt.write_u32::<LittleEndian>(self.sectors_per_fat_32)?;
wrt.write_u16::<LittleEndian>(self.extended_flags)?;
wrt.write_u16::<LittleEndian>(self.fs_version)?;
wrt.write_u32::<LittleEndian>(self.root_dir_first_cluster)?;
wrt.write_u16::<LittleEndian>(self.fs_info_sector)?;
wrt.write_u16::<LittleEndian>(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::<LittleEndian>(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::<LittleEndian>(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<T: Read>(rdr: &mut T) -> io::Result<BootSector> {
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<T: Write>(&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<FatType>) -> 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");
}
}
}

1232
src/byteorder_core_io.rs Normal file

File diff suppressed because it is too large Load Diff

1076
src/dir.rs Normal file

File diff suppressed because it is too large Load Diff

687
src/dir_entry.rs Normal file
View File

@ -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<u32> {
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<u32>, 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<u32> {
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::<LittleEndian>(self.create_time_1)?;
wrt.write_u16::<LittleEndian>(self.create_date)?;
wrt.write_u16::<LittleEndian>(self.access_date)?;
wrt.write_u16::<LittleEndian>(self.first_cluster_hi)?;
wrt.write_u16::<LittleEndian>(self.modify_time)?;
wrt.write_u16::<LittleEndian>(self.modify_date)?;
wrt.write_u16::<LittleEndian>(self.first_cluster_lo)?;
wrt.write_u32::<LittleEndian>(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::<LittleEndian>(*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::<LittleEndian>(*ch)?;
}
wrt.write_u16::<LittleEndian>(self.reserved_0)?;
for ch in self.name_2.iter() {
wrt.write_u16::<LittleEndian>(*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<Self> {
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::<LittleEndian>(&mut data.name_0)?;
data.entry_type = rdr.read_u8()?;
data.checksum = rdr.read_u8()?;
rdr.read_u16_into::<LittleEndian>(&mut data.name_1)?;
data.reserved_0 = rdr.read_u16::<LittleEndian>()?;
rdr.read_u16_into::<LittleEndian>(&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::<LittleEndian>()?,
create_date: rdr.read_u16::<LittleEndian>()?,
access_date: rdr.read_u16::<LittleEndian>()?,
first_cluster_hi: rdr.read_u16::<LittleEndian>()?,
modify_time: rdr.read_u16::<LittleEndian>()?,
modify_date: rdr.read_u16::<LittleEndian>()?,
first_cluster_lo: rdr.read_u16::<LittleEndian>()?,
size: rdr.read_u32::<LittleEndian>()?,
};
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<u32>, 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<T: ReadWriteSeek>(&mut self, fs: &FileSystem<T>) -> io::Result<()> {
if self.dirty {
self.write(fs)?;
self.dirty = false;
}
Ok(())
}
fn write<T: ReadWriteSeek>(&self, fs: &FileSystem<T>) -> 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<u16>,
#[cfg(not(feature = "alloc"))]
pub(crate) lfn_utf16: (),
pub(crate) entry_pos: u64,
pub(crate) offset_range: (u64, u64),
pub(crate) fs: &'a FileSystem<T>,
}
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<u32> {
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<T>) -> 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");
}
}

370
src/file.rs Normal file
View File

@ -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<u32>,
// Note: if offset points between clusters current_cluster is the previous cluster
current_cluster: Option<u32>,
// current position in this file
offset: u32,
// file dir entry editor - None for root dir
entry: Option<DirEntryEditor>,
// file-system reference
fs: &'a FileSystem<T>,
}
impl<'a, T: ReadWriteSeek> File<'a, T> {
pub(crate) fn new(first_cluster: Option<u32>, entry: Option<DirEntryEditor>, fs: &'a FileSystem<T>) -> 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<u64> {
// 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<u32> {
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<usize> {
// 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<u32> {
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<usize> {
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<usize> {
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<u64> {
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)
}
}

1005
src/fs.rs Normal file

File diff suppressed because it is too large Load Diff

112
src/lib.rs Normal file
View File

@ -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::*;

564
src/table.rs Normal file
View File

@ -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<T> {
#[allow(dead_code)]
dummy: [T; 0],
}
type Fat12 = Fat<u8>;
type Fat16 = Fat<u16>;
type Fat32 = Fat<u32>;
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<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<u32>;
fn get<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<FatValue>;
fn set_raw<T: ReadWriteSeek>(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()>;
fn set<T: ReadWriteSeek>(fat: &mut T, cluster: u32, value: FatValue) -> io::Result<()>;
fn find_free<T: ReadSeek>(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result<u32>;
fn count_free<T: ReadSeek>(fat: &mut T, end_cluster: u32) -> io::Result<u32>;
}
fn read_fat<T: ReadSeek>(fat: &mut T, fat_type: FatType, cluster: u32) -> io::Result<FatValue> {
match fat_type {
FatType::Fat12 => Fat12::get(fat, cluster),
FatType::Fat16 => Fat16::get(fat, cluster),
FatType::Fat32 => Fat32::get(fat, cluster),
}
}
fn write_fat<T: ReadWriteSeek>(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<T: ReadSeek>(fat: &mut T, fat_type: FatType, cluster: u32) -> io::Result<Option<u32>> {
let val = read_fat(fat, fat_type, cluster)?;
match val {
FatValue::Data(n) => Ok(Some(n)),
_ => Ok(None),
}
}
fn find_free_cluster<T: ReadSeek>(
fat: &mut T,
fat_type: FatType,
start_cluster: u32,
end_cluster: u32,
) -> io::Result<u32> {
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<T: ReadWriteSeek>(
fat: &mut T,
fat_type: FatType,
prev_cluster: Option<u32>,
hint: Option<u32>,
total_clusters: u32,
) -> io::Result<u32> {
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<T: ReadSeek>(fat: &mut T, fat_type: FatType) -> io::Result<FsStatusFlags> {
// 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<T: ReadSeek>(fat: &mut T, fat_type: FatType, total_clusters: u32) -> io::Result<u32> {
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<T: ReadWriteSeek>(
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::<LittleEndian>(0xFFFF)?;
},
FatType::Fat16 => {
fat.write_u16::<LittleEndian>(media as u16 | 0xFF00)?;
fat.write_u16::<LittleEndian>(0xFFFF)?;
},
FatType::Fat32 => {
fat.write_u32::<LittleEndian>(media as u32 | 0xFFFFF00)?;
fat.write_u32::<LittleEndian>(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<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<u32> {
let fat_offset = cluster + (cluster / 2);
fat.seek(io::SeekFrom::Start(fat_offset as u64))?;
let packed_val = fat.read_u16::<LittleEndian>()?;
Ok(match cluster & 1 {
0 => packed_val & 0x0FFF,
_ => packed_val >> 4,
} as u32)
}
fn get<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<FatValue> {
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<T: ReadWriteSeek>(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<T: ReadWriteSeek>(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::<LittleEndian>()?;
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::<LittleEndian>(new_packed)?;
Ok(())
}
fn find_free<T: ReadSeek>(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result<u32> {
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::<LittleEndian>()?;
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::<LittleEndian>()?,
_ => {
let next_byte = fat.read_u8()? as u16;
(packed_val >> 8) | (next_byte << 8)
},
};
}
}
fn count_free<T: ReadSeek>(fat: &mut T, end_cluster: u32) -> io::Result<u32> {
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::<LittleEndian>(),
_ => 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<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<u32> {
fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?;
Ok(fat.read_u16::<LittleEndian>()? as u32)
}
fn get<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<FatValue> {
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<T: ReadWriteSeek>(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()> {
fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?;
fat.write_u16::<LittleEndian>(raw_value as u16)?;
Ok(())
}
fn set<T: ReadWriteSeek>(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<T: ReadSeek>(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result<u32> {
let mut cluster = start_cluster;
fat.seek(io::SeekFrom::Start((cluster * 2) as u64))?;
while cluster < end_cluster {
let val = fat.read_u16::<LittleEndian>()?;
if val == 0 {
return Ok(cluster);
}
cluster += 1;
}
Err(io::Error::new(io::ErrorKind::Other, "No space left on device"))
}
fn count_free<T: ReadSeek>(fat: &mut T, end_cluster: u32) -> io::Result<u32> {
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::<LittleEndian>()?;
if val == 0 {
count += 1;
}
cluster += 1;
}
Ok(count)
}
}
impl FatTrait for Fat32 {
fn get_raw<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<u32> {
fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?;
Ok(fat.read_u32::<LittleEndian>()?)
}
fn get<T: ReadSeek>(fat: &mut T, cluster: u32) -> io::Result<FatValue> {
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<T: ReadWriteSeek>(fat: &mut T, cluster: u32, raw_value: u32) -> io::Result<()> {
fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?;
fat.write_u32::<LittleEndian>(raw_value)?;
Ok(())
}
fn set<T: ReadWriteSeek>(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<T: ReadSeek>(fat: &mut T, start_cluster: u32, end_cluster: u32) -> io::Result<u32> {
let mut cluster = start_cluster;
fat.seek(io::SeekFrom::Start((cluster * 4) as u64))?;
while cluster < end_cluster {
let val = fat.read_u32::<LittleEndian>()? & 0x0FFFFFFF;
if val == 0 {
return Ok(cluster);
}
cluster += 1;
}
Err(io::Error::new(io::ErrorKind::Other, "No space left on device"))
}
fn count_free<T: ReadSeek>(fat: &mut T, end_cluster: u32) -> io::Result<u32> {
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::<LittleEndian>()? & 0x0FFFFFFF;
if val == 0 {
count += 1;
}
cluster += 1;
}
Ok(count)
}
}
pub(crate) struct ClusterIterator<T: ReadWriteSeek> {
fat: T,
fat_type: FatType,
cluster: Option<u32>,
err: bool,
}
impl<T: ReadWriteSeek> ClusterIterator<T> {
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<u32> {
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<u32> {
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<T: ReadWriteSeek> Iterator for ClusterIterator<T> {
type Item = io::Result<u32>;
fn next(&mut self) -> Option<Self::Item> {
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<T: ReadWriteSeek>(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<_>>(), 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<u8> = 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<u8> = 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<u8> = 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));
}
}

201
src/time.rs Normal file
View File

@ -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<Date> for chrono::Date<Local> {
fn from(date: Date) -> Self {
Local.ymd(date.year as i32, date.month as u32, date.day as u32)
}
}
#[cfg(feature = "chrono")]
impl From<DateTime> for chrono::DateTime<Local> {
fn from(date_time: DateTime) -> Self {
chrono::Date::<Local>::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<chrono::Date<Local>> for Date {
fn from(date: chrono::Date<Local>) -> Self {
Date { year: date.year() as u16, month: date.month() as u16, day: date.day() as u16 }
}
}
#[cfg(feature = "chrono")]
impl From<chrono::DateTime<Local>> for DateTime {
fn from(date_time: chrono::DateTime<Local>) -> 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));
}
}

110
tests/format.rs Normal file
View File

@ -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<BufStream<io::Cursor<Vec<u8>>>>;
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::<Vec<_>>();
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::<Vec<String>>();
assert_eq!(filenames, ["subdir1"]);
let filenames = subdir2.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
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::<Vec<String>>();
assert_eq!(filenames, [".", ".."]);
let filenames = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
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<u8> = 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);
}

267
tests/read.rs Normal file
View File

@ -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<BufStream<fs::File>>;
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::<Vec<_>>();
let short_names = entries.iter().map(|e| e.short_file_name()).collect::<Vec<String>>();
assert_eq!(short_names, ["LONG.TXT", "SHORT.TXT", "VERY", "VERY-L~1"]);
let names = entries.iter().map(|e| e.file_name()).collect::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
let root_names2 = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
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,
)
}

384
tests/write.rs Normal file
View File

@ -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<BufStream<fs::File>>;
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::<Vec<String>>();
assert_eq!(names, [".", "..", "test.txt"]);
root_dir.remove("very/long/path/test.txt").unwrap();
names = dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
assert_eq!(names, [".", ".."]);
assert!(root_dir.remove("very/long/path").is_ok());
names = root_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
assert_eq!(names, [".", "..", "test.txt", "new-file-with-long-name.txt"]);
names = dir.iter().map(|r| r.unwrap().short_file_name()).collect::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
assert_eq!(names, [".", ".."]);
}
// check if new entry is visible in parent
names = parent_dir.iter().map(|r| r.unwrap().file_name()).collect::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<String>>();
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::<Vec<_>>();
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
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::<Vec<_>>();
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
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::<Vec<_>>();
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
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::<Vec<_>>();
let names = entries.iter().map(|r| r.file_name()).collect::<Vec<_>>();
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)
}