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