2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
|
|
|
use core::{slice, iter};
|
2018-05-30 23:43:18 +08:00
|
|
|
use core::{str, char, cmp};
|
2018-05-10 07:00:24 +08:00
|
|
|
|
|
|
|
use io::prelude::*;
|
|
|
|
use io;
|
|
|
|
use io::{ErrorKind, SeekFrom};
|
2017-10-06 22:42:29 +08:00
|
|
|
|
2017-11-08 21:05:14 +08:00
|
|
|
use fs::{FileSystemRef, DiskSlice};
|
2017-10-07 20:56:50 +08:00
|
|
|
use file::File;
|
2018-05-10 21:00:59 +08:00
|
|
|
use dir_entry::{DirEntry, DirEntryData, DirFileEntryData, DirLfnEntryData, FileAttributes, ShortName, DIR_ENTRY_SIZE};
|
2017-09-23 02:50:41 +08:00
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
|
|
|
use dir_entry::{LFN_PART_LEN, LFN_ENTRY_LAST_FLAG};
|
|
|
|
|
|
|
|
#[cfg(all(not(feature = "std"), feature = "alloc"))]
|
2018-05-10 07:00:24 +08:00
|
|
|
use alloc::Vec;
|
|
|
|
|
2017-10-06 22:07:11 +08:00
|
|
|
#[derive(Clone)]
|
2017-10-07 21:00:27 +08:00
|
|
|
pub(crate) enum DirRawStream<'a, 'b: 'a> {
|
2017-10-07 20:56:50 +08:00
|
|
|
File(File<'a, 'b>),
|
|
|
|
Root(DiskSlice<'a, 'b>),
|
2017-09-27 20:05:58 +08:00
|
|
|
}
|
|
|
|
|
2017-10-09 20:59:52 +08:00
|
|
|
impl <'a, 'b> DirRawStream<'a, 'b> {
|
2017-10-21 21:51:19 +08:00
|
|
|
pub(crate) fn abs_pos(&self) -> Option<u64> {
|
2017-10-09 20:59:52 +08:00
|
|
|
match self {
|
2017-10-21 21:51:19 +08:00
|
|
|
&DirRawStream::File(ref file) => file.abs_pos(),
|
|
|
|
&DirRawStream::Root(ref slice) => Some(slice.abs_pos()),
|
2017-10-09 20:59:52 +08:00
|
|
|
}
|
|
|
|
}
|
2017-10-27 20:12:53 +08:00
|
|
|
|
|
|
|
pub(crate) fn first_cluster(&self) -> Option<u32> {
|
|
|
|
match self {
|
|
|
|
&DirRawStream::File(ref file) => file.first_cluster(),
|
|
|
|
&DirRawStream::Root(_) => None,
|
|
|
|
}
|
|
|
|
}
|
2017-10-09 20:59:52 +08:00
|
|
|
}
|
|
|
|
|
2017-10-07 21:00:27 +08:00
|
|
|
impl <'a, 'b> Read for DirRawStream<'a, 'b> {
|
2017-09-27 20:05:58 +08:00
|
|
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
|
|
|
match self {
|
2017-10-07 21:00:27 +08:00
|
|
|
&mut DirRawStream::File(ref mut file) => file.read(buf),
|
|
|
|
&mut DirRawStream::Root(ref mut raw) => raw.read(buf),
|
2017-09-27 20:05:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-15 21:56:01 +08:00
|
|
|
impl <'a, 'b> Write for DirRawStream<'a, 'b> {
|
|
|
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
|
|
|
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(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-07 21:00:27 +08:00
|
|
|
impl <'a, 'b> Seek for DirRawStream<'a, 'b> {
|
2017-09-27 20:05:58 +08:00
|
|
|
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
|
|
|
|
match self {
|
2017-10-07 21:00:27 +08:00
|
|
|
&mut DirRawStream::File(ref mut file) => file.seek(pos),
|
|
|
|
&mut DirRawStream::Root(ref mut raw) => raw.seek(pos),
|
2017-09-27 20:05:58 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-08 23:03:08 +08:00
|
|
|
fn split_path<'c>(path: &'c str) -> (&'c str, Option<&'c str>) {
|
2017-11-08 23:47:12 +08:00
|
|
|
// remove trailing slash and split into 2 components - top-most parent and rest
|
2017-11-08 23:03:08 +08:00
|
|
|
let mut path_split = path.trim_matches('/').splitn(2, "/");
|
2017-11-08 23:47:12 +08:00
|
|
|
let comp = path_split.next().unwrap(); // SAFE: splitn always returns at least one element
|
2017-11-08 23:03:08 +08:00
|
|
|
let rest_opt = path_split.next();
|
|
|
|
(comp, rest_opt)
|
|
|
|
}
|
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// FAT directory
|
2017-10-06 22:07:11 +08:00
|
|
|
#[derive(Clone)]
|
2017-10-07 20:56:50 +08:00
|
|
|
pub struct Dir<'a, 'b: 'a> {
|
2017-10-21 21:51:19 +08:00
|
|
|
stream: DirRawStream<'a, 'b>,
|
2017-10-07 20:56:50 +08:00
|
|
|
fs: FileSystemRef<'a, 'b>,
|
2017-09-24 01:42:09 +08:00
|
|
|
}
|
|
|
|
|
2017-10-07 20:56:50 +08:00
|
|
|
impl <'a, 'b> Dir<'a, 'b> {
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 21:51:19 +08:00
|
|
|
pub(crate) fn new(stream: DirRawStream<'a, 'b>, fs: FileSystemRef<'a, 'b>) -> Dir<'a, 'b> {
|
|
|
|
Dir { stream, fs }
|
2017-09-24 01:42:09 +08:00
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// Creates directory entries iterator
|
2017-10-07 20:56:50 +08:00
|
|
|
pub fn iter(&self) -> DirIter<'a, 'b> {
|
|
|
|
DirIter {
|
2017-10-21 21:51:19 +08:00
|
|
|
stream: self.stream.clone(),
|
2017-10-06 22:07:11 +08:00
|
|
|
fs: self.fs.clone(),
|
2017-10-07 08:16:34 +08:00
|
|
|
err: false,
|
2017-10-06 22:07:11 +08:00
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2018-05-30 23:43:18 +08:00
|
|
|
fn find_entry(&mut self, name: &str, mut short_name_gen: Option<&mut ShortNameGenerator>) -> io::Result<DirEntry<'a, 'b>> {
|
2017-10-06 22:07:11 +08:00
|
|
|
for r in self.iter() {
|
|
|
|
let e = r?;
|
2017-11-08 23:47:12 +08:00
|
|
|
// compare name ignoring case
|
2017-10-06 22:07:11 +08:00
|
|
|
if e.file_name().eq_ignore_ascii_case(name) {
|
|
|
|
return Ok(e);
|
|
|
|
}
|
2018-05-30 23:43:18 +08:00
|
|
|
if let Some(ref mut gen) = short_name_gen {
|
|
|
|
gen.add_existing(e.raw_short_name());
|
|
|
|
}
|
2017-10-06 22:07:11 +08:00
|
|
|
}
|
|
|
|
Err(io::Error::new(ErrorKind::NotFound, "file not found"))
|
2017-09-24 08:10:59 +08:00
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// Opens existing directory
|
2017-10-07 20:56:50 +08:00
|
|
|
pub fn open_dir(&mut self, path: &str) -> io::Result<Dir<'a, 'b>> {
|
2017-11-08 23:03:08 +08:00
|
|
|
let (name, rest_opt) = split_path(path);
|
2018-05-30 23:43:18 +08:00
|
|
|
let e = self.find_entry(name, None)?;
|
2017-10-06 22:07:11 +08:00
|
|
|
match rest_opt {
|
|
|
|
Some(rest) => e.to_dir().open_dir(rest),
|
|
|
|
None => Ok(e.to_dir())
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// Opens existing file.
|
2017-10-07 20:56:50 +08:00
|
|
|
pub fn open_file(&mut self, path: &str) -> io::Result<File<'a, 'b>> {
|
2017-11-08 23:03:08 +08:00
|
|
|
let (name, rest_opt) = split_path(path);
|
2018-05-30 23:43:18 +08:00
|
|
|
let e = self.find_entry(name, None)?;
|
2017-10-06 22:07:11 +08:00
|
|
|
match rest_opt {
|
|
|
|
Some(rest) => e.to_dir().open_file(rest),
|
|
|
|
None => Ok(e.to_file())
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-11-09 03:40:36 +08:00
|
|
|
/// Creates new file or opens existing without truncating.
|
2017-10-21 21:35:26 +08:00
|
|
|
pub fn create_file(&mut self, path: &str) -> io::Result<File<'a, 'b>> {
|
2017-11-08 23:03:08 +08:00
|
|
|
let (name, rest_opt) = split_path(path);
|
2018-05-30 23:43:18 +08:00
|
|
|
let mut short_name_gen = ShortNameGenerator::new(name);
|
|
|
|
let r = self.find_entry(name, Some(&mut short_name_gen));
|
2017-10-21 21:35:26 +08:00
|
|
|
match rest_opt {
|
|
|
|
Some(rest) => r?.to_dir().create_file(rest),
|
|
|
|
None => {
|
|
|
|
match r {
|
2018-05-30 23:43:18 +08:00
|
|
|
Err(ref err) if err.kind() == ErrorKind::NotFound => {
|
|
|
|
let short_name = short_name_gen.generate()?;
|
|
|
|
Ok(self.create_entry(name, short_name, FileAttributes::from_bits_truncate(0), None)?.to_file())
|
|
|
|
},
|
2017-11-07 08:06:52 +08:00
|
|
|
Err(err) => Err(err),
|
|
|
|
Ok(e) => Ok(e.to_file()),
|
2017-10-21 21:35:26 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-27 20:12:53 +08:00
|
|
|
/// Creates new directory or opens existing.
|
|
|
|
pub fn create_dir(&mut self, path: &str) -> io::Result<Dir<'a, 'b>> {
|
2017-11-08 23:03:08 +08:00
|
|
|
let (name, rest_opt) = split_path(path);
|
2018-05-30 23:43:18 +08:00
|
|
|
let mut short_name_gen = ShortNameGenerator::new(name);
|
|
|
|
let r = self.find_entry(name, Some(&mut short_name_gen));
|
2017-10-27 20:12:53 +08:00
|
|
|
match rest_opt {
|
|
|
|
Some(rest) => r?.to_dir().create_dir(rest),
|
|
|
|
None => {
|
|
|
|
match r {
|
2017-11-07 08:06:52 +08:00
|
|
|
Err(ref err) if err.kind() == ErrorKind::NotFound => {
|
2017-11-08 23:47:12 +08:00
|
|
|
// alloc cluster for directory data
|
2017-10-27 20:12:53 +08:00
|
|
|
let cluster = self.fs.alloc_cluster(None)?;
|
2017-11-08 23:47:12 +08:00
|
|
|
// create entry in parent directory
|
2018-05-30 23:43:18 +08:00
|
|
|
let short_name = short_name_gen.generate()?;
|
|
|
|
let entry = self.create_entry(name, short_name, FileAttributes::DIRECTORY, Some(cluster))?;
|
2017-10-27 20:12:53 +08:00
|
|
|
let mut dir = entry.to_dir();
|
2017-11-08 23:47:12 +08:00
|
|
|
// create special entries "." and ".."
|
2018-05-30 23:43:18 +08:00
|
|
|
let dot_sfn = ShortNameGenerator::new(".").generate().unwrap();
|
|
|
|
dir.create_entry(".", dot_sfn, FileAttributes::DIRECTORY, entry.first_cluster())?;
|
|
|
|
let dotdot_sfn = ShortNameGenerator::new("..").generate().unwrap();
|
|
|
|
dir.create_entry("..", dotdot_sfn, FileAttributes::DIRECTORY, self.stream.first_cluster())?;
|
2017-10-27 20:12:53 +08:00
|
|
|
Ok(dir)
|
|
|
|
},
|
2017-11-07 08:06:52 +08:00
|
|
|
Err(err) => Err(err),
|
2017-10-27 20:12:53 +08:00
|
|
|
Ok(e) => Ok(e.to_dir()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-16 04:42:26 +08:00
|
|
|
fn is_empty(&mut self) -> io::Result<bool> {
|
2017-11-08 23:47:12 +08:00
|
|
|
// check if directory contains no files
|
2017-10-16 04:42:26 +08:00
|
|
|
for r in self.iter() {
|
|
|
|
let e = r?;
|
|
|
|
let name = e.file_name();
|
2017-11-08 23:47:12 +08:00
|
|
|
// ignore special entries "." and ".."
|
2017-10-16 04:42:26 +08:00
|
|
|
if name != "." && name != ".." {
|
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(true)
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// Removes existing file or directory.
|
|
|
|
///
|
|
|
|
/// Make sure there is no reference to this file (no File instance) or filesystem corruption
|
|
|
|
/// can happen.
|
2017-10-16 04:42:26 +08:00
|
|
|
pub fn remove(&mut self, path: &str) -> io::Result<()> {
|
2017-11-08 23:03:08 +08:00
|
|
|
let (name, rest_opt) = split_path(path);
|
2018-05-30 23:43:18 +08:00
|
|
|
let e = self.find_entry(name, None)?;
|
2017-10-16 04:42:26 +08:00
|
|
|
match rest_opt {
|
|
|
|
Some(rest) => e.to_dir().remove(rest),
|
|
|
|
None => {
|
|
|
|
trace!("removing {}", path);
|
2017-11-08 23:47:12 +08:00
|
|
|
// in case of directory check if it is empty
|
2017-10-16 04:42:26 +08:00
|
|
|
if e.is_dir() && !e.to_dir().is_empty()? {
|
|
|
|
return Err(io::Error::new(ErrorKind::NotFound, "removing non-empty directory is denied"));
|
|
|
|
}
|
2017-11-08 23:47:12 +08:00
|
|
|
// free directory data
|
2018-05-28 05:31:40 +08:00
|
|
|
if let Some(n) = e.first_cluster() {
|
|
|
|
self.fs.free_cluster_chain(n)?;
|
2017-10-16 04:42:26 +08:00
|
|
|
}
|
2017-11-08 23:47:12 +08:00
|
|
|
// free long and short name entries
|
2017-10-21 21:51:19 +08:00
|
|
|
let mut stream = self.stream.clone();
|
2017-10-16 04:42:26 +08:00
|
|
|
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);
|
2017-11-08 08:23:00 +08:00
|
|
|
data.set_free();
|
2017-10-16 04:42:26 +08:00
|
|
|
stream.seek(SeekFrom::Current(-(DIR_ENTRY_SIZE as i64)))?;
|
|
|
|
data.serialize(&mut stream)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-21 21:35:26 +08:00
|
|
|
fn find_free_entries(&mut self, num_entries: usize) -> io::Result<DirRawStream<'a, 'b>> {
|
2017-10-21 21:51:19 +08:00
|
|
|
let mut stream = self.stream.clone();
|
2017-10-21 21:35:26 +08:00
|
|
|
let mut first_free = 0;
|
|
|
|
let mut num_free = 0;
|
|
|
|
let mut i = 0;
|
|
|
|
loop {
|
2017-10-27 21:03:54 +08:00
|
|
|
let raw_entry = DirEntryData::deserialize(&mut stream)?;
|
|
|
|
if raw_entry.is_end() {
|
2017-11-08 23:47:12 +08:00
|
|
|
// first unused entry - all remaining space can be used
|
2017-10-27 21:03:54 +08:00
|
|
|
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_free() {
|
2017-11-08 23:47:12 +08:00
|
|
|
// free entry - calculate number of free entries in a row
|
2017-10-21 21:35:26 +08:00
|
|
|
if num_free == 0 {
|
|
|
|
first_free = i;
|
|
|
|
}
|
|
|
|
num_free += 1;
|
|
|
|
if num_free == num_entries {
|
2017-11-08 23:47:12 +08:00
|
|
|
// enough space for new file
|
2017-10-21 21:35:26 +08:00
|
|
|
stream.seek(io::SeekFrom::Start(first_free as u64 * DIR_ENTRY_SIZE))?;
|
|
|
|
return Ok(stream);
|
|
|
|
}
|
|
|
|
} else {
|
2017-11-08 23:47:12 +08:00
|
|
|
// used entry - start counting from 0
|
2017-10-21 21:35:26 +08:00
|
|
|
num_free = 0;
|
|
|
|
}
|
|
|
|
i += 1;
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
|
|
|
fn create_lfn_entries(&mut self, name: &str, short_name: &[u8]) -> io::Result<(DirRawStream<'a, 'b>, u64)> {
|
|
|
|
// get short name checksum
|
2017-11-08 23:03:08 +08:00
|
|
|
let lfn_chsum = lfn_checksum(&short_name);
|
2018-05-10 21:00:59 +08:00
|
|
|
// convert long name to UTF-16
|
2017-11-08 23:03:08 +08:00
|
|
|
let lfn_utf16 = name.encode_utf16().collect::<Vec<u16>>();
|
|
|
|
let lfn_iter = LfnEntriesGenerator::new(&lfn_utf16, lfn_chsum);
|
2017-11-08 23:47:12 +08:00
|
|
|
// find space for new entries
|
2017-11-08 23:03:08 +08:00
|
|
|
let num_entries = lfn_iter.len() + 1; // multiple lfn entries + one file entry
|
2017-10-21 21:35:26 +08:00
|
|
|
let mut stream = self.find_free_entries(num_entries)?;
|
|
|
|
let start_pos = stream.seek(io::SeekFrom::Current(0))?;
|
2017-11-08 23:47:12 +08:00
|
|
|
// write LFN entries first
|
2017-11-08 23:03:08 +08:00
|
|
|
for lfn_entry in lfn_iter {
|
2017-10-21 21:35:26 +08:00
|
|
|
lfn_entry.serialize(&mut stream)?;
|
|
|
|
}
|
2018-05-10 21:00:59 +08:00
|
|
|
Ok((stream, start_pos))
|
|
|
|
}
|
|
|
|
#[cfg(not(feature = "alloc"))]
|
|
|
|
fn create_lfn_entries(&mut self, _name: &str, _short_name: &[u8]) -> io::Result<(DirRawStream<'a, 'b>, u64)> {
|
|
|
|
let mut stream = self.find_free_entries(1)?;
|
|
|
|
let start_pos = stream.seek(io::SeekFrom::Current(0))?;
|
|
|
|
Ok((stream, start_pos))
|
|
|
|
}
|
|
|
|
|
2018-05-30 23:43:18 +08:00
|
|
|
fn create_entry(&mut self, name: &str, short_name: [u8; 11], attrs: FileAttributes, first_cluster: Option<u32>) -> io::Result<DirEntry<'a, 'b>> {
|
2018-05-10 21:00:59 +08:00
|
|
|
trace!("create_entry {}", name);
|
|
|
|
// check if name doesn't contain unsupported characters
|
|
|
|
validate_long_name(name)?;
|
|
|
|
// generate long entries
|
|
|
|
let (mut stream, start_pos) = self.create_lfn_entries(&name, &short_name)?;
|
2017-11-08 23:47:12 +08:00
|
|
|
// create and write short name entry
|
2017-11-08 08:23:00 +08:00
|
|
|
let mut raw_entry = DirFileEntryData::new(short_name, attrs);
|
2017-11-09 02:41:05 +08:00
|
|
|
raw_entry.set_first_cluster(first_cluster, self.fs.fat_type());
|
2017-11-08 07:18:31 +08:00
|
|
|
raw_entry.reset_created();
|
2017-11-08 08:23:00 +08:00
|
|
|
raw_entry.reset_accessed();
|
|
|
|
raw_entry.reset_modified();
|
2017-10-21 21:35:26 +08:00
|
|
|
raw_entry.serialize(&mut stream)?;
|
|
|
|
let end_pos = stream.seek(io::SeekFrom::Current(0))?;
|
2017-10-21 21:51:19 +08:00
|
|
|
let abs_pos = stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
2017-11-08 23:47:12 +08:00
|
|
|
// return new logical entry descriptor
|
2018-05-10 21:00:59 +08:00
|
|
|
let short_name = ShortName::new(raw_entry.name());
|
2017-10-21 21:35:26 +08:00
|
|
|
return Ok(DirEntry {
|
|
|
|
data: raw_entry,
|
2018-05-10 21:00:59 +08:00
|
|
|
short_name,
|
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-21 21:35:26 +08:00
|
|
|
lfn: Vec::new(),
|
|
|
|
fs: self.fs,
|
2017-11-08 23:47:12 +08:00
|
|
|
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is absent only for empty file
|
2017-10-21 21:35:26 +08:00
|
|
|
offset_range: (start_pos, end_pos),
|
|
|
|
});
|
|
|
|
}
|
2017-10-06 22:07:11 +08:00
|
|
|
}
|
|
|
|
|
2017-10-21 22:25:04 +08:00
|
|
|
/// Directory entries iterator.
|
2017-10-06 22:07:11 +08:00
|
|
|
#[derive(Clone)]
|
2017-10-07 20:56:50 +08:00
|
|
|
pub struct DirIter<'a, 'b: 'a> {
|
2017-10-21 21:51:19 +08:00
|
|
|
stream: DirRawStream<'a, 'b>,
|
2017-10-07 20:56:50 +08:00
|
|
|
fs: FileSystemRef<'a, 'b>,
|
2017-10-07 08:16:34 +08:00
|
|
|
err: bool,
|
2017-10-06 22:07:11 +08:00
|
|
|
}
|
|
|
|
|
2017-10-07 20:56:50 +08:00
|
|
|
impl <'a, 'b> DirIter<'a, 'b> {
|
2017-10-16 04:42:26 +08:00
|
|
|
fn read_dir_entry(&mut self) -> io::Result<Option<DirEntry<'a, 'b>>> {
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-16 04:42:26 +08:00
|
|
|
let mut lfn_buf = LongNameBuilder::new();
|
2017-10-21 21:51:19 +08:00
|
|
|
let mut offset = self.stream.seek(SeekFrom::Current(0))?;
|
2017-10-16 04:42:26 +08:00
|
|
|
let mut begin_offset = offset;
|
|
|
|
loop {
|
2017-10-27 21:03:54 +08:00
|
|
|
let raw_entry = DirEntryData::deserialize(&mut self.stream)?;
|
2017-10-16 04:42:26 +08:00
|
|
|
offset += DIR_ENTRY_SIZE;
|
|
|
|
match raw_entry {
|
|
|
|
DirEntryData::File(data) => {
|
|
|
|
// Check if this is end of dif
|
|
|
|
if data.is_end() {
|
|
|
|
return Ok(None);
|
|
|
|
}
|
|
|
|
// Check if this is deleted or volume ID entry
|
2017-11-08 23:03:08 +08:00
|
|
|
if data.is_free() || data.is_volume() {
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-16 04:42:26 +08:00
|
|
|
lfn_buf.clear();
|
|
|
|
begin_offset = offset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Get entry position on volume
|
2017-11-08 23:47:12 +08:00
|
|
|
let abs_pos = self.stream.abs_pos().map(|p| p - DIR_ENTRY_SIZE);
|
2017-10-16 04:42:26 +08:00
|
|
|
// Check if LFN checksum is valid
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
lfn_buf.validate_chksum(data.name());
|
2018-05-10 21:00:59 +08:00
|
|
|
// Return directory entry
|
|
|
|
let short_name = ShortName::new(data.name());
|
2017-10-16 04:42:26 +08:00
|
|
|
return Ok(Some(DirEntry {
|
|
|
|
data,
|
2018-05-10 21:00:59 +08:00
|
|
|
short_name,
|
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-16 04:42:26 +08:00
|
|
|
lfn: lfn_buf.to_vec(),
|
|
|
|
fs: self.fs,
|
2017-11-08 23:47:12 +08:00
|
|
|
entry_pos: abs_pos.unwrap(), // SAFE: abs_pos is empty only for empty file
|
2017-10-16 04:42:26 +08:00
|
|
|
offset_range: (begin_offset, offset),
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
DirEntryData::Lfn(data) => {
|
|
|
|
// Check if this is deleted entry
|
2017-10-21 21:51:19 +08:00
|
|
|
if data.is_free() {
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-16 04:42:26 +08:00
|
|
|
lfn_buf.clear();
|
|
|
|
begin_offset = offset;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// Append to LFN buffer
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-16 04:42:26 +08:00
|
|
|
lfn_buf.process(&data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl <'a, 'b> Iterator for DirIter<'a, 'b> {
|
|
|
|
type Item = io::Result<DirEntry<'a, 'b>>;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
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))
|
|
|
|
},
|
2017-09-25 04:12:38 +08:00
|
|
|
}
|
2017-09-24 01:42:09 +08:00
|
|
|
}
|
2017-09-23 02:50:41 +08:00
|
|
|
}
|
2017-09-24 08:24:42 +08:00
|
|
|
|
2017-11-08 23:03:08 +08:00
|
|
|
fn validate_long_name(name: &str) -> io::Result<()> {
|
|
|
|
if name.len() == 0 {
|
|
|
|
return Err(io::Error::new(ErrorKind::InvalidInput, "filename cannot be empty"));
|
|
|
|
}
|
|
|
|
if name.len() > 255 {
|
|
|
|
return Err(io::Error::new(ErrorKind::InvalidInput, "filename is too long"));
|
|
|
|
}
|
|
|
|
for c in name.chars() {
|
|
|
|
match c {
|
|
|
|
'a'...'z' | 'A'...'Z' | '0'...'9' | '\u{80}'...'\u{FFFF}' |
|
|
|
|
'$' | '%' | '\'' | '-' | '_' | '@' | '~' | '`' | '!' | '(' | ')' | '{' | '}' |
|
|
|
|
'.' | ' ' | '+' | ',' | ';' | '=' | '[' | ']' => {},
|
|
|
|
_ => return Err(io::Error::new(ErrorKind::InvalidInput, "invalid character in filename")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Ok(())
|
2017-10-14 21:24:03 +08:00
|
|
|
}
|
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-14 21:24:03 +08:00
|
|
|
fn lfn_checksum(short_name: &[u8]) -> u8 {
|
|
|
|
let mut chksum = 0u8;
|
|
|
|
for i in 0..11 {
|
|
|
|
chksum = (((chksum & 1) << 7) as u16 + (chksum >> 1) as u16 + short_name[i] as u16) as u8;
|
|
|
|
}
|
|
|
|
chksum
|
|
|
|
}
|
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
struct LongNameBuilder {
|
|
|
|
buf: Vec<u16>,
|
|
|
|
chksum: u8,
|
|
|
|
index: u8,
|
|
|
|
}
|
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-10-14 21:24:03 +08:00
|
|
|
impl LongNameBuilder {
|
|
|
|
fn new() -> LongNameBuilder {
|
|
|
|
LongNameBuilder {
|
|
|
|
buf: Vec::<u16>::new(),
|
|
|
|
chksum: 0,
|
|
|
|
index: 0,
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-14 21:24:03 +08:00
|
|
|
fn clear(&mut self) {
|
|
|
|
self.buf.clear();
|
|
|
|
self.index = 0;
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-14 21:24:03 +08:00
|
|
|
fn to_vec(mut self) -> Vec<u16> {
|
|
|
|
if self.index == 1 {
|
|
|
|
self.truncate();
|
|
|
|
self.buf
|
|
|
|
} else {
|
2017-10-15 06:55:19 +08:00
|
|
|
warn!("unfinished LFN sequence {}", self.index);
|
2017-10-14 21:24:03 +08:00
|
|
|
Vec::<u16>::new()
|
|
|
|
}
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-14 21:24:03 +08:00
|
|
|
fn truncate(&mut self) {
|
|
|
|
// Truncate 0 and 0xFFFF characters from LFN buffer
|
|
|
|
let mut lfn_len = self.buf.len();
|
2018-05-28 00:37:33 +08:00
|
|
|
while lfn_len > 0 {
|
2017-10-14 21:24:03 +08:00
|
|
|
match self.buf[lfn_len-1] {
|
|
|
|
0xFFFF | 0 => lfn_len -= 1,
|
|
|
|
_ => break,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
self.buf.truncate(lfn_len);
|
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-14 21:24:03 +08:00
|
|
|
fn process(&mut self, data: &DirLfnEntryData) {
|
2017-11-08 23:03:08 +08:00
|
|
|
let is_last = (data.order() & LFN_ENTRY_LAST_FLAG) != 0;
|
|
|
|
let index = data.order() & 0x1F;
|
2017-10-14 21:24:03 +08:00
|
|
|
if index == 0 {
|
|
|
|
// Corrupted entry
|
2017-11-08 23:03:08 +08:00
|
|
|
warn!("currupted lfn entry! {:x}", data.order());
|
2017-10-14 21:24:03 +08:00
|
|
|
self.clear();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if is_last {
|
|
|
|
// last entry is actually first entry in stream
|
|
|
|
self.index = index;
|
2017-11-08 23:03:08 +08:00
|
|
|
self.chksum = data.checksum();
|
2017-10-14 21:24:03 +08:00
|
|
|
self.buf.resize(index as usize * LFN_PART_LEN, 0);
|
2017-11-08 23:03:08 +08:00
|
|
|
} else if self.index == 0 || index != self.index - 1 || data.checksum() != self.chksum {
|
2017-10-14 21:24:03 +08:00
|
|
|
// Corrupted entry
|
2017-11-08 23:03:08 +08:00
|
|
|
warn!("currupted lfn entry! {:x} {:x} {:x} {:x}", data.order(), self.index, data.checksum(), self.chksum);
|
2017-10-14 21:24:03 +08:00
|
|
|
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
|
2017-11-08 21:05:14 +08:00
|
|
|
data.copy_name_to_slice(&mut self.buf[pos..pos+13]);
|
2017-10-14 21:24:03 +08:00
|
|
|
}
|
2017-10-25 23:20:27 +08:00
|
|
|
|
2017-10-14 21:24:03 +08:00
|
|
|
fn validate_chksum(&mut self, short_name: &[u8]) {
|
|
|
|
let chksum = lfn_checksum(short_name);
|
|
|
|
if chksum != self.chksum {
|
2017-10-15 06:55:19 +08:00
|
|
|
warn!("checksum mismatch {:x} {:x} {:?}", chksum, self.chksum, short_name);
|
2017-10-14 21:24:03 +08:00
|
|
|
self.clear();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-11-08 23:03:08 +08:00
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
struct LfnEntriesGenerator<'a> {
|
|
|
|
name_parts_iter: iter::Rev<slice::Chunks<'a, u16>>,
|
|
|
|
checksum: u8,
|
|
|
|
index: usize,
|
|
|
|
num: usize,
|
|
|
|
ended: bool,
|
|
|
|
}
|
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
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;
|
2017-11-08 23:47:12 +08:00
|
|
|
// create generator using reverse iterator over chunks - first chunk can be shorter
|
2017-11-08 23:03:08 +08:00
|
|
|
LfnEntriesGenerator {
|
|
|
|
checksum,
|
|
|
|
name_parts_iter: name_utf16.chunks(LFN_PART_LEN).rev(),
|
|
|
|
index: 0,
|
|
|
|
num: num_entries,
|
|
|
|
ended: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
impl<'a> Iterator for LfnEntriesGenerator<'a> {
|
|
|
|
type Item = DirLfnEntryData;
|
|
|
|
|
|
|
|
fn next(&mut self) -> Option<Self::Item> {
|
|
|
|
if self.ended {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2017-11-08 23:47:12 +08:00
|
|
|
// get next part from reverse iterator
|
2017-11-08 23:03:08 +08:00
|
|
|
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 {
|
2017-11-08 23:47:12 +08:00
|
|
|
// this is last name part (written as first)
|
2017-11-08 23:03:08 +08:00
|
|
|
order |= LFN_ENTRY_LAST_FLAG;
|
|
|
|
}
|
|
|
|
debug_assert!(order > 0);
|
2017-11-08 23:47:12 +08:00
|
|
|
// name is padded with ' '
|
2017-11-08 23:03:08 +08:00
|
|
|
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 {
|
2017-11-08 23:47:12 +08:00
|
|
|
// name is only zero-terminated if its length is not multiplicity of LFN_PART_LEN
|
2017-11-08 23:03:08 +08:00
|
|
|
lfn_part[name_part.len()] = 0;
|
|
|
|
}
|
2017-11-08 23:47:12 +08:00
|
|
|
// create and return new LFN entry
|
2017-11-08 23:03:08 +08:00
|
|
|
let mut lfn_entry = DirLfnEntryData::new(order, self.checksum);
|
|
|
|
lfn_entry.copy_name_from_slice(&lfn_part);
|
|
|
|
self.index += 1;
|
|
|
|
Some(lfn_entry)
|
|
|
|
},
|
|
|
|
None => {
|
2017-11-08 23:47:12 +08:00
|
|
|
// end of name
|
2017-11-08 23:03:08 +08:00
|
|
|
self.ended = true;
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn size_hint(&self) -> (usize, Option<usize>) {
|
|
|
|
self.name_parts_iter.size_hint()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-08 23:47:12 +08:00
|
|
|
// name_parts_iter is ExactSizeIterator so size_hint returns one limit
|
2018-05-10 21:00:59 +08:00
|
|
|
#[cfg(feature = "alloc")]
|
2017-11-08 23:03:08 +08:00
|
|
|
impl<'a> ExactSizeIterator for LfnEntriesGenerator<'a> {}
|
2018-05-30 07:33:01 +08:00
|
|
|
|
2018-05-30 23:43:18 +08:00
|
|
|
#[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 = [0x20u8; 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 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
|
|
|
|
' ' => continue,
|
|
|
|
// strip leading dots
|
|
|
|
'.' if dst_pos == 0 => continue,
|
|
|
|
// copy allowed characters
|
|
|
|
'A'...'Z' | 'a'...'z' | '0'...'9' |
|
|
|
|
'!' | '#' | '$' | '%' | '&' | '\'' | '(' | ')' |
|
|
|
|
'-' | '@' | '^' | '_' | '`' | '{' | '}' | '~' => 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]) {
|
|
|
|
if short_name == &self.short_name {
|
|
|
|
self.exact_match = true;
|
|
|
|
}
|
|
|
|
let num_suffix = if short_name[6] as char == '~' { (short_name[7] as char).to_digit(10) } else { None };
|
|
|
|
let ext_matches = short_name[8..] == self.short_name[8..];
|
|
|
|
if short_name[..6] == self.short_name[..6] && num_suffix.is_some() && ext_matches {
|
|
|
|
// prefix6-number form (TEXTFI~1.TXT)
|
|
|
|
let num = num_suffix.unwrap(); // SAFE
|
|
|
|
self.long_prefix_bitmap |= 1 << num;
|
|
|
|
}
|
|
|
|
if short_name[..2] == self.short_name[..2] && num_suffix.is_some() && ext_matches {
|
|
|
|
// prefix2-chsum-number form (TE021F~1.TXT)
|
|
|
|
let chksum_res = str::from_utf8(&short_name[2..6]).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 = 0u16;
|
|
|
|
for c in name.chars() {
|
|
|
|
chksum = (chksum >> 1) + ((chksum & 1) << 15) + (c as u16);
|
|
|
|
}
|
|
|
|
chksum
|
|
|
|
}
|
|
|
|
|
|
|
|
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 build_prefixed_name(&self, num: u32, with_chksum: bool) -> [u8; 11] {
|
|
|
|
let mut buf = [0x20u8; 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] = '~' as u8;
|
|
|
|
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;
|
|
|
|
return [c1, c2, c3, c4]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-30 07:33:01 +08:00
|
|
|
#[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() {
|
2018-05-30 23:43:18 +08:00
|
|
|
assert_eq!(&ShortNameGenerator::new("Foo").generate().unwrap(), "FOO ".as_bytes());
|
|
|
|
assert_eq!(&ShortNameGenerator::new("Foo.b").generate().unwrap(), "FOO B ".as_bytes());
|
|
|
|
assert_eq!(&ShortNameGenerator::new("Foo.baR").generate().unwrap(), "FOO BAR".as_bytes());
|
|
|
|
assert_eq!(&ShortNameGenerator::new("Foo+1.baR").generate().unwrap(), "FOO_1~1 BAR".as_bytes());
|
|
|
|
// Note: Wikipedia says its 'VER_12~1.TEX' (TODO: check)
|
|
|
|
assert_eq!(&ShortNameGenerator::new("ver +1.2.text").generate().unwrap(), "VER_1_~1TEX".as_bytes());
|
|
|
|
// Note: Wikipedia says its 'BASHRC~1.SWP' (TODO: check)
|
|
|
|
assert_eq!(&ShortNameGenerator::new(".bashrc.swp").generate().unwrap(), "BASHRC SWP".as_bytes());
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_generate_short_name_collisions() {
|
|
|
|
let mut buf = [0u8; 11];
|
|
|
|
let mut gen = ShortNameGenerator::new("TextFile.Mine.txt");
|
|
|
|
assert_eq!(&gen.generate().unwrap(), "TEXTFI~1TXT".as_bytes());
|
|
|
|
buf.copy_from_slice("TEXTFI~1TXT".as_bytes());
|
|
|
|
gen.add_existing(&buf);
|
|
|
|
assert_eq!(&gen.generate().unwrap(), "TEXTFI~2TXT".as_bytes());
|
|
|
|
buf.copy_from_slice("TEXTFI~2TXT".as_bytes());
|
|
|
|
gen.add_existing(&buf);
|
|
|
|
buf.copy_from_slice("TEXTFI~3TXT".as_bytes());
|
|
|
|
gen.add_existing(&buf);
|
|
|
|
buf.copy_from_slice("TEXTFI~4TXT".as_bytes());
|
|
|
|
gen.add_existing(&buf);
|
|
|
|
assert_eq!(&gen.generate().unwrap(), "TE527D~1TXT".as_bytes());
|
2018-05-30 07:33:01 +08:00
|
|
|
}
|
|
|
|
}
|