commit f461e61ec616af831897224859a03128177db5fb Author: Nicolas Stalder Date: Sun Dec 8 03:55:50 2019 +0100 Import Brandon Edens' bindings. TODO: Make them idiomatic diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml new file mode 100644 index 00000000..3eb0ea92 --- /dev/null +++ b/.builds/archlinux.yml @@ -0,0 +1,29 @@ +image: archlinux +packages: +- arm-none-eabi-gcc +- arm-none-eabi-newlib +sources: +- https://github.com/nickray/littlefs2 + +tasks: + +- setup: | + curl -sSf https://sh.rustup.rs | sh -s -- -y -q + source $HOME/.cargo/env + rustup target add thumbv7em-none-eabi + +- build-thumb: | + cd littlefs2 + source $HOME/.cargo/env + RUSTFLAGS="-D warnings" cargo build --target thumbv7em-none-eabi --release --verbose + +- build-desktop: | + cd littlefs2 + source $HOME/.cargo/env + RUSTFLAGS="-D warnings" cargo build --target thumbv7em-none-eabi --verbose + RUSTFLAGS="-D warnings" cargo build --target thumbv7em-none-eabi --release --verbose + +- test-desktop: | + cd littlefs2 + source $HOME/.cargo/env + cargo test diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..69369904 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..836831db --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "littlefs2" +description = "Rust API for littlefs2" +version = "0.1.0" +authors = ["Nicolas Stalder ", "Brandon Edens "] +edition = "2018" +license = "Apache-2.0 OR MIT" +readme = "README.md" +categories = ["embedded", "filesystem", "no-std"] +repository = "https://github.com/nickray/littlefs2" + +[dependencies] +littlefs2-sys = "0.1.0" +bitflags = "1.0.4" +cty = "0.2.1" diff --git a/LICENSE-APACHE b/LICENSE-APACHE new file mode 100644 index 00000000..2c10c51b --- /dev/null +++ b/LICENSE-APACHE @@ -0,0 +1,201 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following +boilerplate notice, with the fields enclosed by brackets "[]" +replaced with your own identifying information. (Don't include +the brackets!) The text should be enclosed in the appropriate +comment syntax for the file format. We also recommend that a +file or class name and description of purpose be included on the +same "printed page" as the copyright notice for easier +identification within third-party archives. + +Copyright 2019 Nicolas Stalder + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/LICENSE-MIT b/LICENSE-MIT new file mode 100644 index 00000000..1a8a613f --- /dev/null +++ b/LICENSE-MIT @@ -0,0 +1,25 @@ +Copyright (c) 2019 Nicolas Stalder + +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 00000000..abafe30b --- /dev/null +++ b/README.md @@ -0,0 +1,48 @@ +

littlefs2

+
+ + Rust API for littlefs2 + +
+ +
+ +
+ + + Crates.io version + + + + Download + + + + main branch API docs + +
+ +## What is this? + +[![Build Status][build-image]][build-link] + +Work-in-Progress Rust API for [littlefs][littlefs] v2, by Christopher Haster. + +The low-level bindings are generated by the [littlefs2-sys][littlefs2-sys] library. + +[build-image]: https://builds.sr.ht/~nickray/littlefs2.svg +[build-link]: https://builds.sr.ht/~nickray/littlefs2 +[littlefs]: https://github.com/ARMmbed/littlefs +[littlefs2-sys]: https://lib.rs/littlefs2-sys + +Currently, release v2.1.4 is exposed. + +#### License + +LittleFS licensed under [BSD-3-Clause](https://github.com/ARMmbed/littlefs/blob/master/LICENSE.md). +These bindings are licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or [MIT License](LICENSE-MIT) at your option. +
+Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..066c7958 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,1035 @@ +//! Rust interface to Little Filesystem suitable for microcontrollers. +//! +//! This crate provides an interface to LittleFS a small filesystem written in C. Its sibling crate +//! known as littlefs-sys builds and provides the bindings for the underlying C software. +//! +//! A full description of the underlying filesystem technology can be found at: +//! https://os.mbed.com/blog/entry/littlefs-high-integrity-embedded-fs/ +//! https://github.com/ARMmbed/littlefs +//! + +#![allow(dead_code)] +#![allow(unused_variables)] +#![no_std] + +#[macro_use] +extern crate bitflags; + +const READ_SIZE: usize = 256; +const PROG_SIZE: usize = 256; +const CACHE_SIZE: usize = 256; // must be multiple of READ and PROG +const BLOCK_SIZE: usize = 4096; +const BLOCK_COUNT: usize = 32; +const BLOCK_CYCLES: isize = -1; // set >0 for wear leveling +const LOOKAHEAD_SIZE: usize = 128; + +use core::{cmp, fmt, mem, ptr, slice}; +use littlefs2_sys as lfs; + +const NAME_MAX_LEN: usize = lfs::LFS_NAME_MAX as usize; + +/// Definition of errors that might be returned by filesystem functionality. +#[derive(Debug)] +pub enum FsError { + /// Input / output error occurred. + Io, + /// File was corrupt. + Corrupt, + /// No entry found with that name. + Noent, + /// File or directory already exists. + Exist, + /// Path name is not a directory. + NotDir, + /// Path specification is to a directory. + IsDir, + /// Directory was not empty. + NotEmpty, + /// Bad file descriptor. + Badf, + /// File is too big. + FBig, + /// Incorrect value specified to function. + Inval, + /// No space left available for operation. + Nospc, + /// No memory available for completing requirest. + Nomem, + /// Unknown error occurred, integer code specified. + Unknown(i32), +} + +/// Definition of storage interface required by the filesystem. +pub trait Storage { + /// Read data from the storage device. + fn read(&self, off: usize, buf: &mut [u8]) -> Result; + /// Write data to the storage device. + fn write(&mut self, off: usize, data: &[u8]) -> Result; + /// Erase data from the storage device. + fn erase(&mut self, off: usize, len: usize) -> Result; +} + +/// Convert an lfs error to a FsError. +fn lfs_to_fserror(lfs_error: lfs::lfs_error) -> Result<(), FsError> { + match lfs_error { + lfs::lfs_error_LFS_ERR_IO => Err(FsError::Io), + lfs::lfs_error_LFS_ERR_CORRUPT => Err(FsError::Corrupt), + lfs::lfs_error_LFS_ERR_NOENT => Err(FsError::Noent), + lfs::lfs_error_LFS_ERR_EXIST => Err(FsError::Exist), + lfs::lfs_error_LFS_ERR_NOTDIR => Err(FsError::NotDir), + lfs::lfs_error_LFS_ERR_ISDIR => Err(FsError::IsDir), + lfs::lfs_error_LFS_ERR_NOTEMPTY => Err(FsError::NotEmpty), + lfs::lfs_error_LFS_ERR_BADF => Err(FsError::Badf), + lfs::lfs_error_LFS_ERR_FBIG => Err(FsError::FBig), + lfs::lfs_error_LFS_ERR_INVAL => Err(FsError::Inval), + lfs::lfs_error_LFS_ERR_NOSPC => Err(FsError::Nospc), + lfs::lfs_error_LFS_ERR_NOMEM => Err(FsError::Nomem), + lfs::lfs_error_LFS_ERR_OK => Ok(()), + _ => Err(FsError::Unknown(lfs_error)), + } +} + +/// Convert an lfs error to a FsError while encoding a result of usize. +/// This return code is common for file system operations like: +/// read, write, seek. +fn lfs_to_usize_fserror(lfs_error: lfs::lfs_error) -> Result { + let err = lfs_to_fserror(lfs_error); + match err { + Ok(()) => Ok(0), + Err(FsError::Unknown(val)) => Ok(val as usize), + Err(val) => Err(val), + } +} + +/// Definition of starting location for seeking within a file. +/// * Set - seeks from the beginning of the file. +/// * Cur - seeks from the current position in the file +/// * End - seeks from the end of the file. +pub enum Whence { + Set = 0, + Cur = 1, + End = 2, +} + +/// Definition for the type of directory entry which can be a file or directory. +#[derive(Debug, PartialEq)] +pub enum EntryType { + /// Entry is a regular file. + RegularFile, + /// Entry is a directory (location that holds other directories and files). + Directory, +} + +/// Wrapper around an array of u8 representing a filename in ASCII encoding. +pub struct Filename([u8; NAME_MAX_LEN + 1]); + +impl fmt::Debug for Filename { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Filename \"")?; + for b in self.0.iter() { + if *b == 0 { + break; + } + write!(f, "{}", *b as char)?; + } + write!(f, "\"")?; + Ok(()) + } +} + +impl Default for Filename { + fn default() -> Self { + Filename([0; NAME_MAX_LEN + 1]) + } +} + +impl Filename { + /// Convert C character array to u8 filename. + fn from_c_char_array(c_name: *const cty::c_char) -> Self { + let len = strlen(c_name); + let name = unsafe { slice::from_raw_parts(c_name as *const u8, len) }; + let mut filename = Filename::default(); + filename.0[..len].copy_from_slice(&name[..len]); + filename + } +} + +impl PartialEq<&str> for Filename { + fn eq(&self, other: &&str) -> bool { + let mut i = 0; + for s in other.chars() { + if self.0[i] != s as u8 { + return false; + } + i += 1; + } + return true; + } +} + +/// Definition for filesystem info which is read through directory read calls or file stats. +#[derive(Debug)] +pub struct Info { + /// Specification of the type of the info either a directory or file. + pub entry_type: EntryType, + /// The size of the entry; only valid for files. + /// TODO this could be rolled into the entry information. + pub size: usize, + /// The textural name of the file / directory. + pub name: Filename, +} + +impl Info { + /// Convert littlefs info struct into the one made available via the Rust interface. + fn from_lfs_info(lfs_info: lfs::lfs_info) -> Self { + let entry_type = match lfs_info.type_ as u32 { + lfs::lfs_type_LFS_TYPE_REG => EntryType::RegularFile, + lfs::lfs_type_LFS_TYPE_DIR => EntryType::Directory, + _ => { + unreachable!(); + } + }; + + Info { + entry_type: entry_type, + size: lfs_info.size as usize, + name: Filename::from_c_char_array(lfs_info.name.as_ptr()), + } + } +} + +/// Determine the length of a C string by searching for the null terminator. Note that this +/// function will not operate properly if a string is not null terminated. +fn strlen(txt: *const cty::c_char) -> usize { + if txt == ptr::null() { + return 0; + } + + let mut i = 0; + let mut t = txt; + loop { + let v = unsafe { t.read() }; + if v == ('\0' as cty::c_char) { + break; + } + t = unsafe { t.offset(1) }; + i += 1; + } + return i; +} + +bitflags! { + /// Definition of file open flags which can be mixed and matched as appropriate. These definitions + /// are reminiscent of the ones defined by POSIX. + pub struct FileOpenFlags: u32 { + /// Open file in read only mode. + const RDONLY = 0x1; + /// Open file in write only mode. + const WRONLY = 0x2; + /// Open file for reading and writing. + const RDWR = Self::RDONLY.bits | Self::WRONLY.bits; + /// Create the file if it does not exist. + const CREAT = 0x0100; + /// Fail if creating a file that already exists. + const EXCL = 0x0200; + /// Truncate the file if it already exists. + const TRUNC = 0x0400; + /// Open the file in append only mode. + const APPEND = 0x0800; + } +} + +/// Definition of a file handle. File handles are used to interact with a file in the filesystem. +pub struct File { + /// Required scratch pad memory used by LittleFS implementation. + buffer: [u8; PROG_SIZE], + /// Handle to the data used by LittleFS to track file operations. + inner: lfs::lfs_file_t, +} + +impl Default for File { + fn default() -> Self { + File { + buffer: [0u8; PROG_SIZE], + inner: unsafe { mem::uninitialized() }, + } + } +} + +/// Definition of a directory handle which is used to interact with a directory in the filesystem. +pub struct Dir { + /// Handle to the data used by LittleFS to track directory operations. + inner: lfs::lfs_dir_t, +} + +impl Default for Dir { + fn default() -> Self { + Dir { + inner: unsafe { mem::uninitialized() }, + } + } +} + +/// An instance of the Little filesystem. This struct defines the method of interacting with the +/// filesystem and contains all of the data required for the C software. +pub struct LittleFs { + storage: T, + lfs_config: lfs::lfs_config, + lfs: lfs::lfs_t, + read_buffer: [u8; CACHE_SIZE], + prog_buffer: [u8; CACHE_SIZE], + lookahead_buffer: [u8; LOOKAHEAD_SIZE / 8], +} + +/// Interface to the LittleFS. +impl LittleFs { + /// Create a new instance of the LittleFS. + pub fn new(storage: T) -> Self { + LittleFs { + storage: storage, + lfs: unsafe { mem::uninitialized::() }, + lfs_config: unsafe { mem::uninitialized::() }, + read_buffer: [0u8; CACHE_SIZE], + prog_buffer: [0u8; CACHE_SIZE], + lookahead_buffer: [0u8; LOOKAHEAD_SIZE / 8], + } + } + + /// Format the filesystem. + pub fn format(&mut self) -> Result<(), FsError> { + self.lfs_config = self.create_lfs_config(); + let res = unsafe { lfs::lfs_format(&mut self.lfs, &self.lfs_config) }; + lfs_to_fserror(res) + } + + /// Mount the filesystem. + pub fn mount(&mut self) -> Result<(), FsError> { + self.lfs_config = self.create_lfs_config(); + let res = unsafe { lfs::lfs_mount(&mut self.lfs, &self.lfs_config) }; + lfs_to_fserror(res) + } + + /// Unmount the filesystem. + pub fn unmount(mut self) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_unmount(&mut self.lfs) }; + lfs_to_fserror(res) + } + + /// Remove a file or directory. + pub fn remove(&mut self, path: &str) -> Result<(), FsError> { + let mut cstr = [0u8; NAME_MAX_LEN + 1]; + let len = cmp::min(NAME_MAX_LEN, path.len()); + cstr[..len].copy_from_slice(&path.as_bytes()[..len]); + let res = + unsafe { lfs::lfs_remove(&mut self.lfs, &cstr as *const _ as *const cty::c_char) }; + lfs_to_fserror(res) + } + + /// Rename or move a file or directory. + pub fn rename(&mut self, old_path: &str, new_path: &str) -> Result<(), FsError> { + let mut oldpath = [0u8; NAME_MAX_LEN + 1]; + let oldpathlen = cmp::min(NAME_MAX_LEN, old_path.len()); + let mut newpath = [0u8; NAME_MAX_LEN + 1]; + let newpathlen = cmp::min(NAME_MAX_LEN, new_path.len()); + oldpath[..oldpathlen].copy_from_slice(&old_path.as_bytes()[..oldpathlen]); + newpath[..newpathlen].copy_from_slice(&new_path.as_bytes()[..newpathlen]); + let res = unsafe { + lfs::lfs_rename( + &mut self.lfs, + oldpath.as_ptr() as *const cty::c_char, + newpath.as_ptr() as *const cty::c_char, + ) + }; + lfs_to_fserror(res) + } + + /// Populate info for file or directory at specified path. + pub fn stat(&mut self, path: &str, info: &mut Info) -> Result<(), FsError> { + let mut cstr = [0u8; NAME_MAX_LEN + 1]; + let len = cmp::min(NAME_MAX_LEN, path.len()); + cstr[..len].copy_from_slice(&path.as_bytes()[..len]); + + let mut lfs_info: lfs::lfs_info = unsafe { mem::uninitialized() }; + let res = unsafe { + lfs::lfs_stat( + &mut self.lfs, + cstr.as_ptr() as *const cty::c_char, + &mut lfs_info, + ) + }; + + *info = Info::from_lfs_info(lfs_info); + lfs_to_fserror(res) + } + + /// Open a file at the given path. + pub fn file_open( + &mut self, + file: &mut File, + path: &str, + flags: FileOpenFlags, + ) -> Result<(), FsError> { + let mut cstr_path = [0u8; NAME_MAX_LEN]; + let len = cmp::min(NAME_MAX_LEN - 1, path.len()); + cstr_path[..len].copy_from_slice(&path.as_bytes()[..len]); + let file_cfg = lfs::lfs_file_config { + buffer: file.buffer.as_mut_ptr() as *mut cty::c_void, + attrs: core::ptr::null_mut(), + attr_count: 0, + }; + let res = unsafe { + lfs::lfs_file_opencfg( + &mut self.lfs, + &mut file.inner, + cstr_path.as_ptr() as *const cty::c_char, + flags.bits() as i32, + &file_cfg, + ) + }; + lfs_to_fserror(res) + } + + /// Close out the given file. + pub fn file_close(&mut self, mut file: File) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_file_close(&mut self.lfs, &mut file.inner) }; + lfs_to_fserror(res) + } + + /// Synchronize file contents to storage. + pub fn file_sync(&mut self, mut file: File) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_file_sync(&mut self.lfs, &mut file.inner) }; + lfs_to_fserror(res) + } + + /// Read data from file. + pub fn file_read(&mut self, file: &mut File, buf: &mut [u8]) -> Result { + let res = unsafe { + lfs::lfs_file_read( + &mut self.lfs, + &mut file.inner, + buf.as_mut_ptr() as *mut cty::c_void, + buf.len() as u32, + ) + }; + lfs_to_usize_fserror(res) + } + + /// Write data to file. + pub fn file_write(&mut self, file: &mut File, buf: &[u8]) -> Result { + let res = unsafe { + lfs::lfs_file_write( + &mut self.lfs, + &mut file.inner, + buf.as_ptr() as *const cty::c_void, + buf.len() as u32, + ) + }; + lfs_to_usize_fserror(res) + } + + /// Change position of subsequent read / write in file. + pub fn file_seek( + &mut self, + file: &mut File, + off: isize, + whence: Whence, + ) -> Result { + let res = unsafe { + lfs::lfs_file_seek(&mut self.lfs, &mut file.inner, off as i32, whence as i32) + }; + lfs_to_usize_fserror(res) + } + + pub fn file_truncate(&mut self, file: &mut File, size: usize) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_file_truncate(&mut self.lfs, &mut file.inner, size as u32) }; + lfs_to_fserror(res) + } + + /// Tell current position of handle within the file. + pub fn file_tell(&mut self, file: &mut File) -> Result { + let res = unsafe { lfs::lfs_file_tell(&mut self.lfs, &mut file.inner) }; + lfs_to_usize_fserror(res) + } + + /// Rewind file handle to the start of the file. + pub fn file_rewind(&mut self, file: &mut File) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_file_rewind(&mut self.lfs, &mut file.inner) }; + lfs_to_fserror(res) + } + + /// Return total number of bytes in file. + pub fn file_size(&mut self, file: &mut File) -> Result { + let res = unsafe { lfs::lfs_file_size(&mut self.lfs, &mut file.inner) }; + lfs_to_usize_fserror(res) + } + + /// Create a new directory. + pub fn mkdir(&mut self, path: &str) -> Result<(), FsError> { + let mut cstr_path = [0u8; NAME_MAX_LEN + 1]; + let len = cmp::min(NAME_MAX_LEN, path.len()); + cstr_path[..len].copy_from_slice(&path.as_bytes()[..len]); + + let res = + unsafe { lfs::lfs_mkdir(&mut self.lfs, cstr_path.as_ptr() as *const cty::c_char) }; + lfs_to_fserror(res) + } + + /// Open a directory. + pub fn dir_open(&mut self, dir: &mut Dir, path: &str) -> Result<(), FsError> { + let mut cstr_path = [0u8; NAME_MAX_LEN + 1]; + let len = cmp::min(NAME_MAX_LEN, path.len()); + cstr_path[..len].copy_from_slice(&path.as_bytes()[..len]); + + let res = unsafe { + lfs::lfs_dir_open( + &mut self.lfs, + &mut dir.inner, + cstr_path.as_ptr() as *const cty::c_char, + ) + }; + lfs_to_fserror(res) + } + + /// Close a directory. + pub fn dir_close(&mut self, mut dir: Dir) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_dir_close(&mut self.lfs, &mut dir.inner) }; + lfs_to_fserror(res) + } + + /// Read contents of a directory. + pub fn dir_read(&mut self, dir: &mut Dir) -> Result, FsError> { + let mut lfs_info = unsafe { mem::uninitialized() }; + let res = unsafe { lfs::lfs_dir_read(&mut self.lfs, &mut dir.inner, &mut lfs_info) }; + let err = lfs_to_fserror(res); + match err { + Err(FsError::Unknown(1)) => { + return Ok(Some(Info::from_lfs_info(lfs_info))); + } + Ok(()) => { + return Ok(None); + } + Err(err) => { + return Err(err); + } + } + } + + /// Change the position within the directory. + pub fn dir_seek(&mut self, dir: &mut Dir, offset: isize) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_dir_seek(&mut self.lfs, &mut dir.inner, offset as u32) }; + lfs_to_fserror(res) + } + + /// Report position within the directory. + pub fn dir_tell(&mut self, dir: &mut Dir) -> Result { + let res = unsafe { lfs::lfs_dir_tell(&mut self.lfs, &mut dir.inner) }; + lfs_to_usize_fserror(res) + } + + /// Rewrite directory handle back to start of directory. + pub fn dir_rewind(&mut self, dir: &mut Dir) -> Result<(), FsError> { + let res = unsafe { lfs::lfs_dir_rewind(&mut self.lfs, &mut dir.inner) }; + lfs_to_fserror(res) + } + + /// Create instance of lfs configuration. + fn create_lfs_config(&mut self) -> lfs::lfs_config { + lfs::lfs_config { + context: self as *mut _ as *mut cty::c_void, + read: Some(>::lfs_config_read), + prog: Some(>::lfs_config_prog), + erase: Some(>::lfs_config_erase), + sync: Some(>::lfs_config_sync), + read_size: READ_SIZE as u32, + prog_size: PROG_SIZE as u32, + block_size: BLOCK_SIZE as u32, + block_count: BLOCK_COUNT as u32, + block_cycles: BLOCK_CYCLES as i32, + lookahead_size: LOOKAHEAD_SIZE as u32, + cache_size: CACHE_SIZE as u32, + read_buffer: (&mut self.read_buffer) as *mut _ as *mut cty::c_void, + prog_buffer: (&mut self.prog_buffer) as *mut _ as *mut cty::c_void, + lookahead_buffer: (&mut self.lookahead_buffer) as *mut _ as *mut cty::c_void, + name_max: 0, + file_max: 0, + attr_max: 0, + } + } + + /// C callback interface used by LittleFS to read data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_read( + c: *const lfs::lfs_config, + block: lfs::lfs_block_t, + off: lfs::lfs_off_t, + buffer: *mut cty::c_void, + size: lfs::lfs_size_t, + ) -> cty::c_int { + let littlefs: &mut LittleFs = unsafe { mem::transmute((*c).context) }; + assert!(!c.is_null()); + let block_size = unsafe { c.read().block_size }; + let off = (block * block_size + off) as usize; + let buf: &mut [u8] = unsafe { slice::from_raw_parts_mut(buffer as *mut u8, size as usize) }; + + // TODO + littlefs.storage.read(off, buf).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to program data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_prog( + c: *const lfs::lfs_config, + block: lfs::lfs_block_t, + off: lfs::lfs_off_t, + buffer: *const cty::c_void, + size: lfs::lfs_size_t, + ) -> cty::c_int { + let littlefs: &mut LittleFs = unsafe { mem::transmute((*c).context) }; + assert!(!c.is_null()); + let block_size = unsafe { c.read().block_size }; + let off = (block * block_size + off) as usize; + let buf: &[u8] = unsafe { slice::from_raw_parts(buffer as *const u8, size as usize) }; + + // TODO + littlefs.storage.write(off, buf).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to erase data with the lower level system below the + /// filesystem. + extern "C" fn lfs_config_erase( + c: *const lfs::lfs_config, + block: lfs::lfs_block_t, + ) -> cty::c_int { + let littlefs: &mut LittleFs = unsafe { mem::transmute((*c).context) }; + let off = block as usize * BLOCK_SIZE; + + // TODO + littlefs.storage.erase(off, BLOCK_SIZE).unwrap(); + 0 + } + + /// C callback interface used by LittleFS to sync data with the lower level interface below the + /// filesystem. Note that this function currently does nothing. + extern "C" fn lfs_config_sync(c: *const lfs::lfs_config) -> i32 { + // Do nothing; we presume that data is synchronized. + 0 + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + /// Default flash erase value. + const ERASE_VALUE: u8 = 0xFF; + + const STORAGE_SIZE: usize = 131072; + struct RamStorage { + buf: [u8; STORAGE_SIZE], + } + + impl Default for RamStorage { + fn default() -> Self { + RamStorage { + buf: [ERASE_VALUE; STORAGE_SIZE], + } + } + } + + impl Storage for RamStorage { + fn read(&self, off: usize, buf: &mut [u8]) -> Result { + for i in 0..buf.len() { + if off + i >= self.buf.len() { + break; + } + buf[i] = self.buf[off + i]; + } + Ok(buf.len()) + } + + fn write(&mut self, off: usize, data: &[u8]) -> Result { + for i in 0..data.len() { + if off + i >= self.buf.len() { + break; + } + self.buf[off + i] = data[i]; + } + Ok(data.len()) + } + + fn erase(&mut self, off: usize, len: usize) -> Result { + for byte in &mut self.buf[off..off + len] { + *byte = ERASE_VALUE; + } + Ok(len) + } + } + + #[test] + fn test_create_littlefs() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + } + + #[test] + fn test_mount_littlefs() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + lfs.unmount().unwrap(); + } + + #[test] + fn test_mkdir() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + lfs.mkdir("/foo").unwrap(); + + let mut dir = Default::default(); + lfs.dir_open(&mut dir, "/").unwrap(); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, ".."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "foo"); + let info = lfs.dir_read(&mut dir).unwrap(); + assert!(info.is_none()); + let info = lfs.dir_read(&mut dir).unwrap(); + assert!(info.is_none()); + lfs.dir_close(dir).unwrap(); + + lfs.unmount().unwrap(); + } + + #[test] + fn test_create_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + lfs.unmount().unwrap(); + } + + #[test] + fn test_write_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let sz = lfs.file_write(&mut file, b"hello world!").unwrap(); + assert_ne!(sz, 0); + lfs.file_close(file).unwrap(); + lfs.unmount().unwrap(); + } + + #[test] + fn test_read_write_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let write_sz = lfs.file_write(&mut file, b"hello world!").unwrap(); + assert_ne!(write_sz, 0); + + let file_sz = lfs.file_size(&mut file).unwrap(); + assert_eq!(file_sz, write_sz); + + lfs.file_close(file).unwrap(); + + let mut file = Default::default(); + lfs.file_open(&mut file, "/foo.txt", FileOpenFlags::RDWR) + .unwrap(); + let mut buf = [0u8; 32]; + let read_sz = lfs.file_read(&mut file, &mut buf).unwrap(); + assert_ne!(read_sz, 0); + lfs.file_close(file).unwrap(); + lfs.unmount().unwrap(); + + assert_eq!(read_sz, write_sz); + + assert_eq!(&buf[..read_sz], b"hello world!"); + } + + #[test] + fn test_lfs_seek() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let write_sz = lfs.file_write(&mut file, b"hello world!").unwrap(); + assert_ne!(write_sz, 0); + lfs.file_close(file).unwrap(); + + let mut file = Default::default(); + lfs.file_open(&mut file, "/foo.txt", FileOpenFlags::RDWR) + .unwrap(); + // Seek forward pass the hello + lfs.file_seek(&mut file, 6, Whence::Set).unwrap(); + let mut buf = [0u8; 32]; + let read_sz = lfs.file_read(&mut file, &mut buf).unwrap(); + assert_ne!(read_sz, 0); + lfs.file_close(file).unwrap(); + + lfs.unmount().unwrap(); + + assert_eq!(read_sz, 6); + assert_eq!(&buf[..6], b"world!"); + } + + #[test] + fn test_lfs_truncate() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let write_sz = lfs.file_write(&mut file, b"hello world!").unwrap(); + assert_ne!(write_sz, 0); + + lfs.file_truncate(&mut file, 0).unwrap(); + lfs.file_close(file).unwrap(); + + let mut file = Default::default(); + lfs.file_open(&mut file, "/foo.txt", FileOpenFlags::RDWR) + .unwrap(); + let mut buf = [0u8; 32]; + let read_sz = lfs.file_read(&mut file, &mut buf).unwrap(); + assert_eq!(read_sz, 0); + lfs.file_close(file).unwrap(); + + lfs.unmount().unwrap(); + } + + #[test] + fn test_lfs_tell() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let write_sz = lfs.file_write(&mut file, b"hello world!").unwrap(); + assert_ne!(write_sz, 0); + + let tell_sz = lfs.file_tell(&mut file).unwrap(); + assert_eq!(tell_sz, write_sz); + + lfs.file_rewind(&mut file).unwrap(); + let tell_sz = lfs.file_tell(&mut file).unwrap(); + assert_eq!(tell_sz, 0); + + lfs.file_close(file).unwrap(); + lfs.unmount().unwrap(); + } + + #[test] + fn test_write_big_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let mut bytes = [0u8; 256]; + for i in 0..255 { + bytes[i] = i as u8; + } + for i in 0..128 { + let sz = lfs.file_write(&mut file, &bytes).unwrap(); + assert_eq!(sz, 256); + } + lfs.file_close(file).unwrap(); + + lfs.unmount().unwrap(); + } + + #[test] + fn test_erase_big_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let mut bytes = [0u8; 256]; + for i in 0..255 { + bytes[i] = i as u8; + } + for i in 0..128 { + let sz = lfs.file_write(&mut file, &bytes).unwrap(); + assert_eq!(sz, 256); + } + lfs.file_close(file).unwrap(); + + lfs.remove("/foo.txt").unwrap(); + lfs.unmount().unwrap(); + } + + #[test] + fn test_rename_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let mut bytes = [0u8; 256]; + for i in 0..255 { + bytes[i] = i as u8; + } + for i in 0..128 { + let sz = lfs.file_write(&mut file, &bytes).unwrap(); + assert_eq!(sz, 256); + } + lfs.file_close(file).unwrap(); + + let mut dir = Default::default(); + lfs.dir_open(&mut dir, "/").unwrap(); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, ".."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "foo.txt"); + let info = lfs.dir_read(&mut dir).unwrap(); + assert!(info.is_none()); + lfs.dir_close(dir).unwrap(); + + lfs.rename("/foo.txt", "/bar.txt").unwrap(); + + let mut dir = Default::default(); + lfs.dir_open(&mut dir, "/").unwrap(); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, ".."); + let info = lfs.dir_read(&mut dir).unwrap(); + assert_eq!(info.unwrap().name, "bar.txt"); + let info = lfs.dir_read(&mut dir).unwrap(); + assert!(info.is_none()); + lfs.dir_close(dir).unwrap(); + + lfs.unmount().unwrap(); + } + + #[test] + fn test_validate_big_file() { + let storage = RamStorage::default(); + let mut lfs = LittleFs::new(storage); + lfs.format().unwrap(); + lfs.mount().unwrap(); + let mut file = Default::default(); + lfs.file_open( + &mut file, + "/foo.txt", + FileOpenFlags::RDWR | FileOpenFlags::CREAT, + ) + .unwrap(); + let mut bytes = [0u8; 256]; + for i in 0..256 { + bytes[i] = i as u8; + } + for i in 0..128 { + let sz = lfs.file_write(&mut file, &bytes).unwrap(); + assert_eq!(sz, 256); + } + lfs.file_close(file).unwrap(); + + let mut file = Default::default(); + lfs.file_open(&mut file, "/foo.txt", FileOpenFlags::RDWR) + .unwrap(); + loop { + let mut buf = [0u8; 256]; + let sz = lfs.file_read(&mut file, &mut buf).unwrap(); + if sz == 0 { + break; + } + assert_eq!(sz, 256); + for i in 0..256 { + assert_eq!(buf[i], bytes[i]); + } + } + lfs.file_close(file).unwrap(); + + lfs.unmount().unwrap(); + } + + #[test] + fn test_lfs_info() { + let mut lfs_info = lfs::lfs_info { + type_: lfs::lfs_type_LFS_TYPE_REG as u8, + size: 4, + name: [0; (NAME_MAX_LEN) + 1], + }; + let filename = b"/foo.txt"; + let u8slice = unsafe { &*(filename as *const [u8] as *const [i8]) }; + lfs_info.name[..filename.len()].copy_from_slice(u8slice); + + let info = Info::from_lfs_info(lfs_info); + assert_eq!(info.entry_type, EntryType::RegularFile); + assert_eq!(info.size, 4); + assert_eq!(info.name, "/foo.txt"); + } +}