You've already forked archr-flasher
mirror of
https://github.com/archr-linux/archr-flasher.git
synced 2026-07-12 18:19:47 -07:00
flasher: configure the boot partition inside the image, before writing
The user's diagnostic log proved the macOS write and verify complete successfully and only the post-flash mount of the boot partition fails: the ArchR boot FAT is a below-spec-cluster-count FAT32 (the geometry the vendor U-Boots require), and the macOS msdos driver refuses to mount it, misreading it as FAT16. Stop mounting it at all. The panel overlay, variant marker and the soysauce extlinux switch are now written into the image itself before flashing, through a vendored fatfs whose FAT type detection trusts the BPB layout instead of the cluster-count heuristic (see vendor/fatfs/VENDORED.md); the Linux kernel and mtools make the same call. A raw .img supplied by the user is copied to temp first so the original file is never modified, and the verify pass now covers the configuration bytes too. The post-write mount path and its err:mount_boot failure mode are gone.
This commit is contained in:
Generated
+23
@@ -61,8 +61,10 @@ name = "archr-flasher"
|
||||
version = "1.3.6"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"fatfs",
|
||||
"flate2",
|
||||
"fs2",
|
||||
"fscommon",
|
||||
"libc",
|
||||
"md-5",
|
||||
"reqwest 0.12.28",
|
||||
@@ -325,8 +327,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"wasm-bindgen",
|
||||
"windows-link 0.2.1",
|
||||
]
|
||||
|
||||
@@ -736,6 +740,16 @@ version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fatfs"
|
||||
version = "0.3.6"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.7"
|
||||
@@ -855,6 +869,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fscommon"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "315ce685aca5ddcc5a3e7e436ef47d4a5d0064462849b6f0f628c28140103531"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futf"
|
||||
version = "0.1.5"
|
||||
|
||||
@@ -35,6 +35,11 @@ sha2 = "0.10"
|
||||
fs2 = "0.4"
|
||||
md-5 = "0.10"
|
||||
libc = "0.2"
|
||||
# Vendored with a relaxed FAT type check: the ArchR boot partition is a
|
||||
# below-minimum-cluster-count FAT32 the vendor U-Boots require, which
|
||||
# stock fatfs (and the macOS msdos driver) refuse. See vendor/fatfs/VENDORED.md.
|
||||
fatfs = { path = "vendor/fatfs" }
|
||||
fscommon = "0.1"
|
||||
tauri-plugin-updater = "2"
|
||||
tauri-plugin-process = "2"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ use std::time::Duration;
|
||||
use tauri::{AppHandle, Emitter};
|
||||
use serde::Serialize;
|
||||
use crate::flash::{emit_progress, validate_device, check_temp_space, poll_flash_progress, decompress_xz, decompress_gz};
|
||||
use crate::rawwrite_unix::{receive_fd_from_helper, write_image_to_raw_fd, configure_boot_volume, FdHelperError};
|
||||
use crate::rawwrite_unix::{receive_fd_from_helper, write_image_to_raw_fd, patch_boot_partition, FdHelperError};
|
||||
use crate::diaglog::DiagLog;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -64,19 +64,39 @@ fn flash_core(
|
||||
check_temp_space(image_path)?;
|
||||
}
|
||||
|
||||
// Step 1: decompress in user space (with progress)
|
||||
let img_to_flash = if needs_decompress {
|
||||
// Step 1: materialize a private working copy of the image. macOS
|
||||
// cannot mount the ArchR boot FAT (below-spec FAT32 the vendor
|
||||
// U-Boots require), so the panel overlay and variant marker are
|
||||
// patched into the image itself before writing; the user's original
|
||||
// file is never modified.
|
||||
let temp_img = std::env::temp_dir().join("archr-flash-temp.img");
|
||||
if needs_decompress {
|
||||
emit_progress(app, 0.0, "decompressing");
|
||||
let temp_img = std::env::temp_dir().join("archr-flash-temp.img");
|
||||
if is_xz {
|
||||
decompress_xz(app, image_path, &temp_img)?;
|
||||
} else {
|
||||
decompress_gz(app, image_path, &temp_img)?;
|
||||
}
|
||||
temp_img
|
||||
} else {
|
||||
PathBuf::from(image_path)
|
||||
};
|
||||
diag.push_str("raw image given, copying to temp for the boot patch\n");
|
||||
check_temp_space(image_path)?;
|
||||
fs::copy(image_path, &temp_img)
|
||||
.map_err(|e| format!("err:image_patch copying image to temp: {}", e))?;
|
||||
}
|
||||
let img_to_flash = temp_img;
|
||||
|
||||
// Step 1b: install the overlay, variant and extlinux switch inside
|
||||
// the image. The later verify pass then covers these bytes too.
|
||||
diag.push_str(&format!(
|
||||
"patching boot partition (dtbo: {}, variant: {})\n",
|
||||
custom_dtbo_path, variant
|
||||
));
|
||||
patch_boot_partition(&img_to_flash, Path::new(custom_dtbo_path), variant)
|
||||
.map_err(|e| {
|
||||
diag.push_str(&format!("boot patch failed: {}\n", e));
|
||||
e
|
||||
})?;
|
||||
diag.push_str("boot partition patched\n");
|
||||
|
||||
let image_size = fs::metadata(&img_to_flash).map(|m| m.len()).unwrap_or(0);
|
||||
|
||||
@@ -167,34 +187,13 @@ fn flash_core(
|
||||
stop.store(true, Ordering::Relaxed);
|
||||
let _ = poll_handle.join();
|
||||
let _ = fs::remove_file(&progress_file);
|
||||
if needs_decompress {
|
||||
let _ = fs::remove_file(&img_to_flash);
|
||||
}
|
||||
let _ = fs::remove_file(&img_to_flash);
|
||||
|
||||
write_result?;
|
||||
verify_result?;
|
||||
|
||||
// Step 6: remount and configure the boot partition as the plain user.
|
||||
emit_progress(app, 92.0, "configuring");
|
||||
let boot_vol = match mount_boot_volume(device, diag) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
let _ = Command::new("diskutil").args(["eject", device]).status();
|
||||
return Err(format!(
|
||||
"err:mount_boot could not mount {}s1 after flashing (image was written)",
|
||||
device
|
||||
));
|
||||
}
|
||||
};
|
||||
diag.push_str(&format!("boot volume: {}\n", boot_vol.display()));
|
||||
|
||||
let cfg = configure_boot_volume(&boot_vol, Path::new(custom_dtbo_path), variant);
|
||||
if let Err(e) = &cfg {
|
||||
diag.push_str(&format!("configure failed: {}\n", e));
|
||||
let _ = Command::new("diskutil").args(["eject", device]).status();
|
||||
}
|
||||
cfg?;
|
||||
|
||||
// The boot partition was configured inside the image before the
|
||||
// write, so nothing needs the quirky FAT mounted here. Just eject.
|
||||
emit_progress(app, 95.0, "syncing");
|
||||
let _ = Command::new("sync").status();
|
||||
let _ = Command::new("diskutil").args(["eject", device]).status();
|
||||
@@ -222,34 +221,3 @@ fn diskutil_device_bytes(device: &str) -> Option<u64> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Remount the freshly written disk and resolve the boot (s1) mount point.
|
||||
/// mountDisk mounts every volume; the explicit s1 mount covers the case
|
||||
/// where diskutil skips the boot FAT on the first pass.
|
||||
#[cfg(target_os = "macos")]
|
||||
fn mount_boot_volume(device: &str, diag: &mut DiagLog) -> Option<PathBuf> {
|
||||
use std::process::Command;
|
||||
let part = format!("{}s1", device);
|
||||
for attempt in 1..=5 {
|
||||
thread::sleep(Duration::from_secs(2));
|
||||
let _ = Command::new("diskutil").args(["mountDisk", device]).output();
|
||||
let _ = Command::new("diskutil").args(["mount", &part]).output();
|
||||
thread::sleep(Duration::from_secs(1));
|
||||
if let Ok(out) = Command::new("diskutil").args(["info", &part]).output() {
|
||||
let text = String::from_utf8_lossy(&out.stdout).to_string();
|
||||
for line in text.lines() {
|
||||
if line.contains("Mount Point:") {
|
||||
if let Some(mp) = line.splitn(2, ':').nth(1) {
|
||||
let mp = mp.trim();
|
||||
if !mp.is_empty() && Path::new(mp).is_dir() {
|
||||
return Some(PathBuf::from(mp));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if attempt == 5 {
|
||||
diag.push_str(&format!("diskutil info {}:\n{}\n", part, text));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
+115
-21
@@ -16,7 +16,7 @@
|
||||
// this file verbatim inside a mod block, where they are not allowed;
|
||||
// the allow(dead_code) lives on the mod declaration in main.rs.
|
||||
|
||||
use std::fs::{self, File};
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Seek, SeekFrom, Write};
|
||||
use std::os::fd::{FromRawFd, OwnedFd, RawFd};
|
||||
use std::path::Path;
|
||||
@@ -259,29 +259,123 @@ fn full_sync(f: &File) -> std::io::Result<()> {
|
||||
f.sync_all()
|
||||
}
|
||||
|
||||
/// Post-flash boot partition setup, done as the regular user on the
|
||||
/// mounted FAT volume: install the panel overlay, record the variant and
|
||||
/// switch the soysauce extlinux config, mirroring the Linux flash path.
|
||||
pub fn configure_boot_volume(boot: &Path, dtbo: &Path, variant: &str) -> Result<(), String> {
|
||||
|
||||
/// Install the panel overlay, variant marker and (for soysauce) the
|
||||
/// extlinux switch INSIDE the image file, before anything touches the
|
||||
/// card. This is how the macOS path configures the boot partition: the
|
||||
/// ArchR boot FAT is a below-spec-cluster-count FAT32 that the macOS
|
||||
/// msdos driver refuses to mount, so post-flash configuration through
|
||||
/// the OS is impossible there. Patching the image up front also means a
|
||||
/// subsequent verify pass covers the configuration bytes too.
|
||||
pub fn patch_boot_partition(image: &Path, dtbo: &Path, variant: &str) -> Result<(), String> {
|
||||
use std::io::Read;
|
||||
|
||||
if !dtbo.is_file() {
|
||||
return Err(format!("Custom DTBO not found at {}", dtbo.display()));
|
||||
return Err(format!("err:image_patch custom DTBO not found at {}", dtbo.display()));
|
||||
}
|
||||
let overlays = boot.join("overlays");
|
||||
fs::create_dir_all(&overlays)
|
||||
.map_err(|e| format!("Cannot create overlays dir: {}", e))?;
|
||||
fs::copy(dtbo, overlays.join("mipi-panel.dtbo"))
|
||||
.map_err(|e| format!("Cannot copy overlay: {}", e))?;
|
||||
fs::write(boot.join("variant"), variant)
|
||||
.map_err(|e| format!("Cannot write variant: {}", e))?;
|
||||
if variant == "soysauce" {
|
||||
let conf = boot.join("extlinux/extlinux.conf");
|
||||
let soy = boot.join("extlinux/extlinux.conf.soysauce");
|
||||
if soy.is_file() {
|
||||
fs::copy(&conf, boot.join("extlinux/extlinux.conf.bak"))
|
||||
.map_err(|e| format!("Cannot back up extlinux.conf: {}", e))?;
|
||||
fs::copy(&soy, &conf)
|
||||
.map_err(|e| format!("Cannot switch extlinux.conf: {}", e))?;
|
||||
let mut dtbo_bytes = Vec::new();
|
||||
File::open(dtbo)
|
||||
.and_then(|mut f| f.read_to_end(&mut dtbo_bytes))
|
||||
.map_err(|e| format!("err:image_patch reading dtbo: {}", e))?;
|
||||
|
||||
let mut img = std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open(image)
|
||||
.map_err(|e| format!("err:image_patch opening image: {}", e))?;
|
||||
|
||||
// First MBR partition entry: status byte at 446, type at 446+4,
|
||||
// start LBA (little endian u32) at 446+8, sector count at 446+12.
|
||||
let mut mbr = [0u8; 512];
|
||||
img.read_exact(&mut mbr)
|
||||
.map_err(|e| format!("err:image_patch reading MBR: {}", e))?;
|
||||
if mbr[510] != 0x55 || mbr[511] != 0xAA {
|
||||
return Err("err:image_patch image has no MBR signature".into());
|
||||
}
|
||||
let entry = &mbr[446..462];
|
||||
let ptype = entry[4];
|
||||
// FAT32 LBA (0x0c) or FAT32 CHS (0x0b); everything ArchR ships uses 0x0c.
|
||||
if ptype != 0x0c && ptype != 0x0b {
|
||||
return Err(format!(
|
||||
"err:image_patch first partition is not FAT32 (type 0x{:02x})",
|
||||
ptype
|
||||
));
|
||||
}
|
||||
let start_lba = u32::from_le_bytes(entry[8..12].try_into().unwrap()) as u64;
|
||||
let num_sectors = u32::from_le_bytes(entry[12..16].try_into().unwrap()) as u64;
|
||||
if start_lba == 0 || num_sectors == 0 {
|
||||
return Err("err:image_patch empty boot partition entry".into());
|
||||
}
|
||||
let part_start = start_lba * 512;
|
||||
let part_end = part_start + num_sectors * 512;
|
||||
|
||||
let slice = fscommon::StreamSlice::new(img, part_start, part_end)
|
||||
.map_err(|e| format!("err:image_patch slicing partition: {}", e))?;
|
||||
let fs = fatfs::FileSystem::new(slice, fatfs::FsOptions::new())
|
||||
.map_err(|e| format!("err:image_patch opening FAT: {}", e))?;
|
||||
{
|
||||
let root = fs.root_dir();
|
||||
|
||||
let overlays = root
|
||||
.create_dir("overlays")
|
||||
.map_err(|e| format!("err:image_patch overlays dir: {}", e))?;
|
||||
let mut f = overlays
|
||||
.create_file("mipi-panel.dtbo")
|
||||
.map_err(|e| format!("err:image_patch creating overlay: {}", e))?;
|
||||
f.truncate()
|
||||
.map_err(|e| format!("err:image_patch truncating overlay: {}", e))?;
|
||||
f.write_all(&dtbo_bytes)
|
||||
.map_err(|e| format!("err:image_patch writing overlay: {}", e))?;
|
||||
drop(f);
|
||||
|
||||
let mut v = root
|
||||
.create_file("variant")
|
||||
.map_err(|e| format!("err:image_patch creating variant: {}", e))?;
|
||||
v.truncate()
|
||||
.map_err(|e| format!("err:image_patch truncating variant: {}", e))?;
|
||||
v.write_all(variant.as_bytes())
|
||||
.map_err(|e| format!("err:image_patch writing variant: {}", e))?;
|
||||
drop(v);
|
||||
|
||||
if variant == "soysauce" {
|
||||
let extlinux = root
|
||||
.open_dir("extlinux")
|
||||
.map_err(|e| format!("err:image_patch extlinux dir: {}", e))?;
|
||||
let mut soys = Vec::new();
|
||||
match extlinux.open_file("extlinux.conf.soysauce") {
|
||||
Ok(mut f) => {
|
||||
f.read_to_end(&mut soys)
|
||||
.map_err(|e| format!("err:image_patch reading soysauce conf: {}", e))?;
|
||||
}
|
||||
// Older images without the alternate config keep the default.
|
||||
Err(_) => {}
|
||||
}
|
||||
if !soys.is_empty() {
|
||||
let mut cur = Vec::new();
|
||||
extlinux
|
||||
.open_file("extlinux.conf")
|
||||
.and_then(|mut f| f.read_to_end(&mut cur).map(|_| ()))
|
||||
.map_err(|e| format!("err:image_patch reading extlinux.conf: {}", e))?;
|
||||
let mut bak = extlinux
|
||||
.create_file("extlinux.conf.bak")
|
||||
.map_err(|e| format!("err:image_patch creating conf backup: {}", e))?;
|
||||
bak.truncate()
|
||||
.map_err(|e| format!("err:image_patch truncating conf backup: {}", e))?;
|
||||
bak.write_all(&cur)
|
||||
.map_err(|e| format!("err:image_patch writing conf backup: {}", e))?;
|
||||
drop(bak);
|
||||
let mut conf = extlinux
|
||||
.open_file("extlinux.conf")
|
||||
.map_err(|e| format!("err:image_patch opening extlinux.conf: {}", e))?;
|
||||
conf.truncate()
|
||||
.map_err(|e| format!("err:image_patch truncating extlinux.conf: {}", e))?;
|
||||
conf.write_all(&soys)
|
||||
.map_err(|e| format!("err:image_patch writing extlinux.conf: {}", e))?;
|
||||
drop(conf);
|
||||
}
|
||||
}
|
||||
}
|
||||
fs.unmount()
|
||||
.map_err(|e| format!("err:image_patch unmounting FAT: {}", e))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
{
|
||||
"title": "Arch R Flasher",
|
||||
"width": 680,
|
||||
"height": 480,
|
||||
"minWidth": 600,
|
||||
"minHeight": 400,
|
||||
"height": 500,
|
||||
"minWidth": 680,
|
||||
"minHeight": 490,
|
||||
"resizable": true,
|
||||
"center": true
|
||||
}
|
||||
|
||||
Vendored
+14
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
target
|
||||
tmp
|
||||
Cargo.lock
|
||||
Vendored
+67
@@ -0,0 +1,67 @@
|
||||
Changelog
|
||||
=========
|
||||
|
||||
0.3.6 (2023-01-17)
|
||||
------------------------
|
||||
Bug fixes:
|
||||
* Create directory entry with `VOLUME_ID` attribute when formatting if volume label was set in `FormatVolumeOptions`
|
||||
* Fix `format_volume` function panicking in debug build for FAT12 volumes with size below 1 MB
|
||||
|
||||
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
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "fatfs"
|
||||
version = "0.3.6"
|
||||
authors = ["Rafał Harabień <rafalh92@outlook.com>"]
|
||||
repository = "https://github.com/rafalh/rust-fatfs"
|
||||
readme = "README.md"
|
||||
keywords = ["fat", "filesystem", "no_std"]
|
||||
categories = ["filesystem"]
|
||||
license = "MIT"
|
||||
description = """
|
||||
FAT filesystem library.
|
||||
"""
|
||||
exclude = [
|
||||
"resources/*",
|
||||
]
|
||||
|
||||
[badges]
|
||||
travis-ci = { repository = "rafalh/rust-fatfs" }
|
||||
|
||||
[features]
|
||||
# Use Rust std library
|
||||
std = ["byteorder/std"]
|
||||
# Use dynamic allocation - required for LFN support. When used without std please enable core_io/collections
|
||||
alloc = []
|
||||
# Default features
|
||||
default = ["chrono", "std", "alloc"]
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1", default-features = false }
|
||||
bitflags = "1.0"
|
||||
log = "0.4"
|
||||
chrono = { version = "0.4", optional = true }
|
||||
core_io = { version = "0.1", optional = true }
|
||||
|
||||
# dev-dependencies removed: tests and examples are not vendored
|
||||
Vendored
+19
@@ -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.
|
||||
Vendored
+73
@@ -0,0 +1,73 @@
|
||||
Rust FAT FS
|
||||
===========
|
||||
|
||||
[](https://github.com/rafalh/rust-fatfs/actions/workflows/ci.yml)
|
||||
[](./LICENSE.txt)
|
||||
[](https://crates.io/crates/fatfs)
|
||||
[](https://docs.rs/fatfs)
|
||||
[](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`.
|
||||
Vendored
+18
@@ -0,0 +1,18 @@
|
||||
# Vendored fatfs 0.3.6
|
||||
|
||||
Vendored copy of https://github.com/rafalh/rust-fatfs (MIT, see
|
||||
LICENSE.txt) with one behavioral change, marked with `ArchR:` comments
|
||||
in `src/boot_sector.rs` and `src/fs.rs`:
|
||||
|
||||
The ArchR boot partition is FAT32 formatted with a cluster count below
|
||||
the spec minimum of 65525. The vendor U-Boots shipped on R36S boards
|
||||
only boot from exactly this geometry, so the image cannot be
|
||||
"corrected". Upstream fatfs (like the macOS msdos driver) refuses to
|
||||
touch such a volume because the FAT type inferred from the cluster
|
||||
count disagrees with the BPB layout. The patch makes the BPB layout
|
||||
signal (sectors_per_fat_16 == 0 means FAT32) authoritative, which is
|
||||
what the Linux kernel driver and mtools do in practice.
|
||||
|
||||
Used by the flasher to install the panel overlay and variant marker
|
||||
inside the image before writing, so no OS-level mount of the quirky
|
||||
FAT is ever needed.
|
||||
+887
File diff suppressed because it is too large
Load Diff
+1232
File diff suppressed because it is too large
Load Diff
Vendored
+1076
File diff suppressed because it is too large
Load Diff
+687
File diff suppressed because it is too large
Load Diff
Vendored
+370
@@ -0,0 +1,370 @@
|
||||
use core;
|
||||
use core::cmp;
|
||||
use io;
|
||||
use io::prelude::*;
|
||||
use io::{ErrorKind, SeekFrom};
|
||||
|
||||
use dir_entry::DirEntryEditor;
|
||||
use fs::{FileSystem, ReadWriteSeek};
|
||||
use time::{Date, DateTime};
|
||||
|
||||
const MAX_FILE_SIZE: u32 = core::u32::MAX;
|
||||
|
||||
/// A FAT filesystem file object used for reading and writing data.
|
||||
///
|
||||
/// This struct is created by the `open_file` or `create_file` methods on `Dir`.
|
||||
pub struct File<'a, T: ReadWriteSeek + 'a> {
|
||||
// Note first_cluster is None if file is empty
|
||||
first_cluster: Option<u32>,
|
||||
// Note: if offset points between clusters current_cluster is the previous cluster
|
||||
current_cluster: Option<u32>,
|
||||
// current position in this file
|
||||
offset: u32,
|
||||
// file dir entry editor - None for root dir
|
||||
entry: Option<DirEntryEditor>,
|
||||
// file-system reference
|
||||
fs: &'a FileSystem<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> File<'a, T> {
|
||||
pub(crate) fn new(first_cluster: Option<u32>, entry: Option<DirEntryEditor>, fs: &'a FileSystem<T>) -> Self {
|
||||
File {
|
||||
first_cluster,
|
||||
entry,
|
||||
fs,
|
||||
current_cluster: None, // cluster before first one
|
||||
offset: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_dir_entry_after_write(&mut self) {
|
||||
let offset = self.offset;
|
||||
if let Some(ref mut e) = self.entry {
|
||||
let now = self.fs.options.time_provider.get_current_date_time();
|
||||
e.set_modified(now);
|
||||
if e.inner().size().map_or(false, |s| offset > s) {
|
||||
e.set_size(offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Truncate file in current position.
|
||||
pub fn truncate(&mut self) -> io::Result<()> {
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.set_size(self.offset);
|
||||
if self.offset == 0 {
|
||||
e.set_first_cluster(None, self.fs.fat_type());
|
||||
}
|
||||
}
|
||||
if self.offset > 0 {
|
||||
debug_assert!(self.current_cluster.is_some());
|
||||
// if offset is not 0 current cluster cannot be empty
|
||||
self.fs.truncate_cluster_chain(self.current_cluster.unwrap()) // SAFE
|
||||
} else {
|
||||
debug_assert!(self.current_cluster.is_none());
|
||||
if let Some(n) = self.first_cluster {
|
||||
self.fs.free_cluster_chain(n)?;
|
||||
self.first_cluster = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn abs_pos(&self) -> Option<u64> {
|
||||
// Returns current position relative to filesystem start
|
||||
// Note: when between clusters it returns position after previous cluster
|
||||
match self.current_cluster {
|
||||
Some(n) => {
|
||||
let cluster_size = self.fs.cluster_size();
|
||||
let offset_mod_cluster_size = self.offset % cluster_size;
|
||||
let offset_in_cluster = if offset_mod_cluster_size == 0 {
|
||||
// position points between clusters - we are returning previous cluster so
|
||||
// offset must be set to the cluster size
|
||||
cluster_size
|
||||
} else {
|
||||
offset_mod_cluster_size
|
||||
};
|
||||
let offset_in_fs = self.fs.offset_from_cluster(n) + u64::from(offset_in_cluster);
|
||||
Some(offset_in_fs)
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn flush_dir_entry(&mut self) -> io::Result<()> {
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.flush(self.fs)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Sets date and time of creation for this file.
|
||||
///
|
||||
/// Note: it is set to a value from the `TimeProvider` when creating a file.
|
||||
/// Deprecated: if needed implement a custom `TimeProvider`.
|
||||
#[deprecated]
|
||||
pub fn set_created(&mut self, date_time: DateTime) {
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.set_created(date_time);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets date of last access for this file.
|
||||
///
|
||||
/// Note: it is overwritten by a value from the `TimeProvider` on every file read operation.
|
||||
/// Deprecated: if needed implement a custom `TimeProvider`.
|
||||
#[deprecated]
|
||||
pub fn set_accessed(&mut self, date: Date) {
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.set_accessed(date);
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets date and time of last modification for this file.
|
||||
///
|
||||
/// Note: it is overwritten by a value from the `TimeProvider` on every file write operation.
|
||||
/// Deprecated: if needed implement a custom `TimeProvider`.
|
||||
#[deprecated]
|
||||
pub fn set_modified(&mut self, date_time: DateTime) {
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.set_modified(date_time);
|
||||
}
|
||||
}
|
||||
|
||||
fn size(&self) -> Option<u32> {
|
||||
match self.entry {
|
||||
Some(ref e) => e.inner().size(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_dir(&self) -> bool {
|
||||
match self.entry {
|
||||
Some(ref e) => e.inner().is_dir(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn bytes_left_in_file(&self) -> Option<usize> {
|
||||
// Note: seeking beyond end of file is not allowed so overflow is impossible
|
||||
self.size().map(|s| (s - self.offset) as usize)
|
||||
}
|
||||
|
||||
fn set_first_cluster(&mut self, cluster: u32) {
|
||||
self.first_cluster = Some(cluster);
|
||||
if let Some(ref mut e) = self.entry {
|
||||
e.set_first_cluster(self.first_cluster, self.fs.fat_type());
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn first_cluster(&self) -> Option<u32> {
|
||||
self.first_cluster
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> Drop for File<'a, T> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(err) = self.flush() {
|
||||
error!("flush failed {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Note: derive cannot be used because of invalid bounds. See: https://github.com/rust-lang/rust/issues/26925
|
||||
impl<'a, T: ReadWriteSeek> Clone for File<'a, T> {
|
||||
fn clone(&self) -> Self {
|
||||
File {
|
||||
first_cluster: self.first_cluster,
|
||||
current_cluster: self.current_cluster,
|
||||
offset: self.offset,
|
||||
entry: self.entry.clone(),
|
||||
fs: self.fs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> Read for File<'a, T> {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let cluster_size = self.fs.cluster_size();
|
||||
let current_cluster_opt = if self.offset % cluster_size == 0 {
|
||||
// next cluster
|
||||
match self.current_cluster {
|
||||
None => self.first_cluster,
|
||||
Some(n) => {
|
||||
let r = self.fs.cluster_iter(n).next();
|
||||
match r {
|
||||
Some(Err(err)) => return Err(err),
|
||||
Some(Ok(n)) => Some(n),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
}
|
||||
} else {
|
||||
self.current_cluster
|
||||
};
|
||||
let current_cluster = match current_cluster_opt {
|
||||
Some(n) => n,
|
||||
None => return Ok(0),
|
||||
};
|
||||
let offset_in_cluster = self.offset % cluster_size;
|
||||
let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
|
||||
let bytes_left_in_file = self.bytes_left_in_file().unwrap_or(bytes_left_in_cluster);
|
||||
let read_size = cmp::min(cmp::min(buf.len(), bytes_left_in_cluster), bytes_left_in_file);
|
||||
if read_size == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
trace!("read {} bytes in cluster {}", read_size, current_cluster);
|
||||
let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64);
|
||||
let read_bytes = {
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset_in_fs))?;
|
||||
disk.read(&mut buf[..read_size])?
|
||||
};
|
||||
if read_bytes == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
self.offset += read_bytes as u32;
|
||||
self.current_cluster = Some(current_cluster);
|
||||
|
||||
if let Some(ref mut e) = self.entry {
|
||||
if self.fs.options.update_accessed_date {
|
||||
let now = self.fs.options.time_provider.get_current_date();
|
||||
e.set_accessed(now);
|
||||
}
|
||||
}
|
||||
Ok(read_bytes)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> Write for File<'a, T> {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let cluster_size = self.fs.cluster_size();
|
||||
let offset_in_cluster = self.offset % cluster_size;
|
||||
let bytes_left_in_cluster = (cluster_size - offset_in_cluster) as usize;
|
||||
let bytes_left_until_max_file_size = (MAX_FILE_SIZE - self.offset) as usize;
|
||||
let write_size = cmp::min(buf.len(), bytes_left_in_cluster);
|
||||
let write_size = cmp::min(write_size, bytes_left_until_max_file_size);
|
||||
// Exit early if we are going to write no data
|
||||
if write_size == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
// Mark the volume 'dirty'
|
||||
self.fs.set_dirty_flag(true)?;
|
||||
// Get cluster for write possibly allocating new one
|
||||
let current_cluster = if self.offset % cluster_size == 0 {
|
||||
// next cluster
|
||||
let next_cluster = match self.current_cluster {
|
||||
None => self.first_cluster,
|
||||
Some(n) => {
|
||||
let r = self.fs.cluster_iter(n).next();
|
||||
match r {
|
||||
Some(Err(err)) => return Err(err),
|
||||
Some(Ok(n)) => Some(n),
|
||||
None => None,
|
||||
}
|
||||
},
|
||||
};
|
||||
match next_cluster {
|
||||
Some(n) => n,
|
||||
None => {
|
||||
// end of chain reached - allocate new cluster
|
||||
let new_cluster = self.fs.alloc_cluster(self.current_cluster, self.is_dir())?;
|
||||
trace!("allocated cluser {}", new_cluster);
|
||||
if self.first_cluster.is_none() {
|
||||
self.set_first_cluster(new_cluster);
|
||||
}
|
||||
new_cluster
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// self.current_cluster should be a valid cluster
|
||||
match self.current_cluster {
|
||||
Some(n) => n,
|
||||
None => panic!("Offset inside cluster but no cluster allocated"),
|
||||
}
|
||||
};
|
||||
trace!("write {} bytes in cluster {}", write_size, current_cluster);
|
||||
let offset_in_fs = self.fs.offset_from_cluster(current_cluster) + (offset_in_cluster as u64);
|
||||
let written_bytes = {
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.seek(SeekFrom::Start(offset_in_fs))?;
|
||||
disk.write(&buf[..write_size])?
|
||||
};
|
||||
if written_bytes == 0 {
|
||||
return Ok(0);
|
||||
}
|
||||
// some bytes were writter - update position and optionally size
|
||||
self.offset += written_bytes as u32;
|
||||
self.current_cluster = Some(current_cluster);
|
||||
self.update_dir_entry_after_write();
|
||||
Ok(written_bytes)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
self.flush_dir_entry()?;
|
||||
let mut disk = self.fs.disk.borrow_mut();
|
||||
disk.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ReadWriteSeek> Seek for File<'a, T> {
|
||||
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
||||
let mut new_pos = match pos {
|
||||
SeekFrom::Current(x) => self.offset as i64 + x,
|
||||
SeekFrom::Start(x) => x as i64,
|
||||
SeekFrom::End(x) => {
|
||||
let size = self.size().expect("cannot seek from end if size is unknown") as i64;
|
||||
size + x
|
||||
},
|
||||
};
|
||||
if new_pos < 0 {
|
||||
return Err(io::Error::new(ErrorKind::InvalidInput, "Seek to a negative offset"));
|
||||
}
|
||||
if let Some(s) = self.size() {
|
||||
if new_pos > s as i64 {
|
||||
info!("seek beyond end of file");
|
||||
new_pos = s as i64;
|
||||
}
|
||||
}
|
||||
let mut new_pos = new_pos as u32;
|
||||
trace!("file seek {} -> {} - entry {:?}", self.offset, new_pos, self.entry);
|
||||
if new_pos == self.offset {
|
||||
// position is the same - nothing to do
|
||||
return Ok(self.offset as u64);
|
||||
}
|
||||
// get number of clusters to seek (favoring previous cluster in corner case)
|
||||
let cluster_count = (self.fs.clusters_from_bytes(new_pos as u64) as i32 - 1) as isize;
|
||||
let old_cluster_count = (self.fs.clusters_from_bytes(self.offset as u64) as i32 - 1) as isize;
|
||||
let new_cluster = if new_pos == 0 {
|
||||
None
|
||||
} else if cluster_count == old_cluster_count {
|
||||
self.current_cluster
|
||||
} else {
|
||||
match self.first_cluster {
|
||||
Some(n) => {
|
||||
let mut cluster = n;
|
||||
let mut iter = self.fs.cluster_iter(n);
|
||||
for i in 0..cluster_count {
|
||||
cluster = match iter.next() {
|
||||
Some(r) => r?,
|
||||
None => {
|
||||
// chain ends before new position - seek to end of last cluster
|
||||
new_pos = self.fs.bytes_from_clusters((i + 1) as u32) as u32;
|
||||
break;
|
||||
},
|
||||
};
|
||||
}
|
||||
Some(cluster)
|
||||
},
|
||||
None => {
|
||||
// empty file - always seek to 0
|
||||
new_pos = 0;
|
||||
None
|
||||
},
|
||||
}
|
||||
};
|
||||
self.offset = new_pos as u32;
|
||||
self.current_cluster = new_cluster;
|
||||
Ok(self.offset as u64)
|
||||
}
|
||||
}
|
||||
Vendored
+1013
File diff suppressed because it is too large
Load Diff
Vendored
+112
@@ -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::*;
|
||||
Vendored
+564
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user