diff --git a/include/Consts.h b/include/Consts.h index 386552b3..b4728fc0 100644 --- a/include/Consts.h +++ b/include/Consts.h @@ -1,47 +1,44 @@ #pragma once /* -* Здесь хранятся все настройки прошивки +* Main consts */ -#define firmware_version "2.3.4" +#define FIRMWARE_VERSION "2.3.4" #define NUM_BUTTONS 6 -#define mb_4_of_memory 1 -#define wifi_mqtt_reconnecting 20000 -#define blink_pin 2 -#define tank_level_times_to_send 10 //после скольки выстрелов делать отправку данных -#define statistics_update 1000 * 60 * 60 * 2 +#define LED_PIN 2 +#define FLASH_4MB true +#define MQTT_RECONNECT_INTERVAL 20000 +// 1000 * 60 * 60 * 2 +#define TELEMETRY_UPDATE_INTERVAL 0 /* * Optional */ -//#define OTA_enable -//#define MDNS_enable -//#define WS_enable -//#define layout_in_ram -#define UDP_enable +//#define OTA_UPDATES_ENABLED +//#define MDNS_ENABLED +//#define WEBSOCKET_ENABLED +//#define LAYOUT_IN_RAM +#define UDP_ENABLED /* * Sensor */ -#define level_enable -#define analog_enable -#define dallas_enable -#define dht_enable -#define bmp_enable -#define bme_enable - -/* -* Logging -*/ -#define logging_enable +#define TANK_LEVEL_SAMPLES 10 +#define LEVEL_ENABLED +#define ANALOG_ENABLED +#define DALLAS_ENABLED +#define DHT_ENABLED +#define BMP_ENABLED +#define BME_ENABLED /* * Gears */ -#define stepper_enable -#define servo_enable +#define STEPPER_ENABLED +#define SERVO_ENABLED /* * Other */ -#define serial_enable -#define push_enable +#define LOGGING_ENABLED +#define SERIAL_ENABLED +#define PUSH_ENABLED diff --git a/include/ESP32_Spec.h b/include/ESP32.h similarity index 58% rename from include/ESP32_Spec.h rename to include/ESP32.h index eed2b9b9..1662e906 100644 --- a/include/ESP32_Spec.h +++ b/include/ESP32.h @@ -1,18 +1,26 @@ #pragma once #ifdef ESP32 -#include +// don't change order +#include "WiFi.h" +// + +#include "ESPAsyncWebServer.h" +#include "SPIFFSEditor.h" +// don't change order #include #include #include #include -#include + +// #include #include -#ifdef MDNS_enable +#ifdef MDNS_ENABLED #include #endif extern AsyncUDP udp; + #endif \ No newline at end of file diff --git a/include/ESP8266_Spec.h b/include/ESP8266.h similarity index 67% rename from include/ESP8266_Spec.h rename to include/ESP8266.h index f62cd71e..770692e6 100644 --- a/include/ESP8266_Spec.h +++ b/include/ESP8266.h @@ -1,13 +1,16 @@ #pragma once #ifdef ESP8266 + #include #include #include +#include +#include +#include #include #include - -#ifdef MDNS_enable +#ifdef MDNS_ENABLED #include #endif diff --git a/include/Global.h b/include/Global.h index 66f54aba..8fb75cb8 100644 --- a/include/Global.h +++ b/include/Global.h @@ -3,17 +3,13 @@ /* * Libraries */ - #include #include -#include -#include -#include -#include +#include "ESP32.h" +#include "ESP8266.h" +// #include "Consts.h" -#include "ESP32_Spec.h" -#include "ESP8266_Spec.h" #include "Errors.h" #include "GyverFilters.h" #include "UptimeInterval.h" @@ -34,7 +30,7 @@ #include #include #include -#ifdef OTA_enable +#ifdef OTA_UPDATES_ENABLED #include #endif @@ -42,7 +38,7 @@ * Objects.h(без данных) */ -#ifdef WS_enable +#ifdef WEBSOCKET_ENABLED extern AsyncWebSocket ws; //extern AsyncEventSource events; #endif @@ -164,9 +160,6 @@ extern void Scenario_init(); extern void txtExecution(String file); extern void stringExecution(String str); -// FileSystem -extern void File_system_init(); - // i2c_bu extern void do_i2c_scanning(); extern String i2c_scan(); @@ -187,7 +180,7 @@ extern void choose_log_date_and_send(); // Main extern void setChipId(); -extern void getMemoryLoad(String text); +extern void printMemoryStatus(String text); extern void saveConfig(); extern String getURL(const String &urls); diff --git a/include/Utils/FileUtils.h b/include/Utils/FileUtils.h index b42c9112..c5f4e5a3 100644 --- a/include/Utils/FileUtils.h +++ b/include/Utils/FileUtils.h @@ -1,7 +1,30 @@ #pragma once #include -#include + +#include "FS.h" + +#ifdef ESP32 +#include "LITTLEFS.h" +#define LittleFS LITTLEFS +#endif +#ifdef ESP8266 +#include +#endif +/* +* Инициализация ФС +*/ +bool fileSystemInit(); + +/* +* Удалить файл +*/ +void removeFile(const String filename); + +/* +* Открыть файл на позиции +*/ +File seekFile(const String filename, size_t position = 0); /* * Чтение строки из файла diff --git a/include/Utils/TimeUtils.h b/include/Utils/TimeUtils.h index c702a1e8..5e3a37d3 100644 --- a/include/Utils/TimeUtils.h +++ b/include/Utils/TimeUtils.h @@ -1,7 +1,9 @@ #pragma once #include +#ifdef ESP8266 #include +#endif void Time_Init(); diff --git a/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h b/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h index 6e16c234..3b2fbd96 100644 --- a/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h +++ b/lib/ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -64,6 +64,8 @@ class AsyncResponseStream; #ifdef ESP8266WEBSERVER_H #define HTTP_ENUM #endif +#endif + #ifndef HTTP_ENUM typedef enum { HTTP_GET = 0b00000001, HTTP_POST = 0b00000010, @@ -75,7 +77,6 @@ typedef enum { HTTP_GET = 0b00000001, HTTP_ANY = 0b01111111, } WebRequestMethod; #endif -#endif //if this value is returned when asked for data, packet will not be sent and you will be asked for data again #define RESPONSE_TRY_AGAIN 0xFFFFFFFF diff --git a/lib/ESPAsyncWebServer/src/SPIFFSEditor.h b/lib/ESPAsyncWebServer/src/SPIFFSEditor.h index ca03d4a7..00e87293 100644 --- a/lib/ESPAsyncWebServer/src/SPIFFSEditor.h +++ b/lib/ESPAsyncWebServer/src/SPIFFSEditor.h @@ -1,7 +1,10 @@ #ifndef SPIFFSEditor_H_ #define SPIFFSEditor_H_ #include + +#ifdef ESP8266 #include +#endif class SPIFFSEditor : public AsyncWebHandler { private: diff --git a/lib/LITTLEFS/LICENSE b/lib/LITTLEFS/LICENSE new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/lib/LITTLEFS/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/lib/LITTLEFS/README.md b/lib/LITTLEFS/README.md new file mode 100644 index 00000000..fa8f6850 --- /dev/null +++ b/lib/LITTLEFS/README.md @@ -0,0 +1,59 @@ +# LITTLEFS +## LittleFS library for arduino-esp32 + +#### Warning: Tested only with git arduino-esp32 #b92c58d core, which is for ESP-IDF 3.3!
With other versions/releases of the core including release 1.0.4, especially against different IDF this will NOT not work. + +- A LittleFS wrapper for Arduino ESP32 of [Mbed LittleFS](https://github.com/ARMmbed/littlefs) +- Based on [ESP-IDF port of joltwallet/esp_littlefs](https://github.com/joltwallet/esp_littlefs) , thank you Brian! +- Functionality is the same and SPIFFS partition scheme and data folder meaning are kept +- You can use either LITTLEFS or SPIFFS but not both simultaneously on given Arduino project +- A PR to embed it to esp32 core is made too. See the [PR status here](https://github.com/espressif/arduino-esp32/pull/4096) + +### Installation + +- Copy LITTLEFS folder to Arduino IDE embedded libraries place +- For Win, the default place of arduino-esp32 core libraries is somewhere like: +```C:\Users\\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\libraries ``` +- Alternatively, you can put it to your usual libraries place + +### Usage + +- In your existing code, replace SPIFFS like this +``` +#define USE_LittleFS + +#include +#ifdef USE_LittleFS + #define SPIFFS LITTLEFS + #include +#else + #include +#endif + ``` +### Differences with SPIFFS (and in this implementation) + +- LittleFS has folders, you my need to tweak your code to iterate files in folders +- Root: /someting = something, so attention to / +- Lower level littlefs library cannot mount on NULL as partition_label name, while SPIFFS can +- Lower level littlefs library does not need maxOpenFiles parameter +- Speed (LITTLEFS_Test.ino) 1048576 bytes written in 16238 ms / 1048576 bytes read in 918 ms +- Speed (SPIFFS_Test.ino) 1048576 bytes written in 65971 ms / 1048576 bytes read in 680 ms + + +### Arduino ESP32 LittleFS filesystem upload tool + +- Download the tool archive from [here](https://github.com/lorol/arduino-esp32littlefs-plugin/raw/master/src/bin/esp32littlefs.jar) +- In your Arduino sketchbook directory, create tools directory if it doesn't exist yet. +- Unpack the tool into tools directory (the path will look like ```/Arduino/tools/ESP32LittleFS/tool/esp32littlefs.jar```). +- You need the [mklittlefs tool](https://github.com/earlephilhower/mklittlefs) Download the [release](https://github.com/earlephilhower/mklittlefs/releases) and copy it to +packages\esp32\tools\mkspiffs\\ or on checkout (dev) environment to: packages\esp32\hardware\esp32\\tools\mklittlefs\ +- Restart Arduino IDE. + +## Credits and license + +- This work is based on [Mbed LittleFS](https://github.com/ARMmbed/littlefs) , [ESP-IDF port of joltwallet/esp_littlefs](https://github.com/joltwallet/esp_littlefs) , [Espressif Arduino core for the ESP32, the ESP-IDF - SPIFFS Library](https://github.com/espressif/arduino-esp32/tree/master/libraries/SPIFFS) +- Licensed under GPL v2 ([text](LICENSE)) + +## To Do + +- Supporting different IDF versions \ No newline at end of file diff --git a/lib/LITTLEFS/examples/LittleFS_test/LittleFS_test.ino b/lib/LITTLEFS/examples/LittleFS_test/LittleFS_test.ino new file mode 100644 index 00000000..b760a401 --- /dev/null +++ b/lib/LITTLEFS/examples/LittleFS_test/LittleFS_test.ino @@ -0,0 +1,182 @@ +#include +#include "FS.h" +#include + +/* You only need to format SPIFFS the first time you run a + test or else use the LITTLEFS plugin to create a partition + https://github.com/me-no-dev/arduino-esp32fs-plugin */ +#define FORMAT_LITTLEFS_IF_FAILED true + +void listDir(fs::FS &fs, const char * dirname, uint8_t levels){ + Serial.printf("Listing directory: %s\r\n", dirname); + + File root = fs.open(dirname); + if(!root){ + Serial.println("- failed to open directory"); + return; + } + if(!root.isDirectory()){ + Serial.println(" - not a directory"); + return; + } + + File file = root.openNextFile(); + while(file){ + if(file.isDirectory()){ + Serial.print(" DIR : "); + Serial.println(file.name()); + if(levels){ + listDir(fs, file.name(), levels -1); + } + } else { + Serial.print(" FILE: "); + Serial.print(file.name()); + Serial.print("\tSIZE: "); + Serial.println(file.size()); + } + file = root.openNextFile(); + } +} + +void readFile(fs::FS &fs, const char * path){ + Serial.printf("Reading file: %s\r\n", path); + + File file = fs.open(path); + if(!file || file.isDirectory()){ + Serial.println("- failed to open file for reading"); + return; + } + + Serial.println("- read from file:"); + while(file.available()){ + Serial.write(file.read()); + } + file.close(); +} + +void writeFile(fs::FS &fs, const char * path, const char * message){ + Serial.printf("Writing file: %s\r\n", path); + + File file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("- failed to open file for writing"); + return; + } + if(file.print(message)){ + Serial.println("- file written"); + } else { + Serial.println("- write failed"); + } + file.close(); +} + +void appendFile(fs::FS &fs, const char * path, const char * message){ + Serial.printf("Appending to file: %s\r\n", path); + + File file = fs.open(path, FILE_APPEND); + if(!file){ + Serial.println("- failed to open file for appending"); + return; + } + if(file.print(message)){ + Serial.println("- message appended"); + } else { + Serial.println("- append failed"); + } + file.close(); +} + +void renameFile(fs::FS &fs, const char * path1, const char * path2){ + Serial.printf("Renaming file %s to %s\r\n", path1, path2); + if (fs.rename(path1, path2)) { + Serial.println("- file renamed"); + } else { + Serial.println("- rename failed"); + } +} + +void deleteFile(fs::FS &fs, const char * path){ + Serial.printf("Deleting file: %s\r\n", path); + if(fs.remove(path)){ + Serial.println("- file deleted"); + } else { + Serial.println("- delete failed"); + } +} + +void testFileIO(fs::FS &fs, const char * path){ + Serial.printf("Testing file I/O with %s\r\n", path); + + static uint8_t buf[512]; + size_t len = 0; + File file = fs.open(path, FILE_WRITE); + if(!file){ + Serial.println("- failed to open file for writing"); + return; + } + + size_t i; + Serial.print("- writing" ); + uint32_t start = millis(); + for(i=0; i<2048; i++){ + if ((i & 0x001F) == 0x001F){ + Serial.print("."); + } + file.write(buf, 512); + } + Serial.println(""); + uint32_t end = millis() - start; + Serial.printf(" - %u bytes written in %u ms\r\n", 2048 * 512, end); + file.close(); + + file = fs.open(path); + start = millis(); + end = start; + i = 0; + if(file && !file.isDirectory()){ + len = file.size(); + size_t flen = len; + start = millis(); + Serial.print("- reading" ); + while(len){ + size_t toRead = len; + if(toRead > 512){ + toRead = 512; + } + file.read(buf, toRead); + if ((i++ & 0x001F) == 0x001F){ + Serial.print("."); + } + len -= toRead; + } + Serial.println(""); + end = millis() - start; + Serial.printf("- %u bytes read in %u ms\r\n", flen, end); + file.close(); + } else { + Serial.println("- failed to open file for reading"); + } +} + +void setup(){ + Serial.begin(115200); + if(!LITTLEFS.begin(FORMAT_LITTLEFS_IF_FAILED)){ + Serial.println("LITTLEFS Mount Failed"); + return; + } + + listDir(LITTLEFS, "/", 0); + writeFile(LITTLEFS, "/hello.txt", "Hello "); + appendFile(LITTLEFS, "/hello.txt", "World!\r\n"); + readFile(LITTLEFS, "/hello.txt"); + renameFile(LITTLEFS, "/hello.txt", "/foo.txt"); + readFile(LITTLEFS, "/foo.txt"); + deleteFile(LITTLEFS, "/foo.txt"); + testFileIO(LITTLEFS, "/test.txt"); + deleteFile(LITTLEFS, "/test.txt"); + Serial.println( "Test complete" ); +} + +void loop(){ + +} diff --git a/lib/LITTLEFS/library.json b/lib/LITTLEFS/library.json new file mode 100644 index 00000000..26c79422 --- /dev/null +++ b/lib/LITTLEFS/library.json @@ -0,0 +1,22 @@ +{ + "name":"LITTLEFS", + "description":"LITTLEFS File System Library for ESP32", + "keywords":"littlefs, spiffs", + "authors": + { + "name": "LL", + "maintainer": true + }, + "repository": + { + "type": "git", + "url": "https://github.com/lorol/LITTLEFS.git" + }, + "version": "1.0", + "license": "LGPL-2.0", + "frameworks": "arduino", + "platforms": "espressif32", + "build": { + "libCompatMode": 2 + } +} \ No newline at end of file diff --git a/lib/LITTLEFS/library.properties b/lib/LITTLEFS/library.properties new file mode 100644 index 00000000..d57d320c --- /dev/null +++ b/lib/LITTLEFS/library.properties @@ -0,0 +1,9 @@ +name=LITTLEFS +version=1.0 +author=LL +maintainer=LL +sentence=ESP32 LITTLEFS File System +paragraph= +category=Data Storage +url= +architectures=esp32 \ No newline at end of file diff --git a/lib/LITTLEFS/src/LITTLEFS.cpp b/lib/LITTLEFS/src/LITTLEFS.cpp new file mode 100644 index 00000000..32dce386 --- /dev/null +++ b/lib/LITTLEFS/src/LITTLEFS.cpp @@ -0,0 +1,106 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. + +static constexpr const char LFS_NAME[] = "spiffs"; + +#include "vfs_api.h" + +extern "C" { +#include +#include +#include +#include "esp_littlefs.h" +} + +#include "LITTLEFS.h" + +using namespace fs; + +LITTLEFSFS::LITTLEFSFS() : FS(FSImplPtr(new VFSImpl())) +{ + +} + +bool LITTLEFSFS::begin(bool formatOnFail, const char * basePath, uint8_t maxOpenFiles) +{ + if(esp_littlefs_mounted(LFS_NAME)){ + log_w("LITTLEFS Already Mounted!"); + return true; + } + + esp_vfs_littlefs_conf_t conf = { + .base_path = basePath, + .partition_label = LFS_NAME, + //.max_files = maxOpenFiles, + .format_if_mount_failed = false + }; + + esp_err_t err = esp_vfs_littlefs_register(&conf); + if(err == ESP_FAIL && formatOnFail){ + if(format()){ + err = esp_vfs_littlefs_register(&conf); + } + } + if(err != ESP_OK){ + log_e("Mounting LITTLEFS failed! Error: %d", err); + return false; + } + _impl->mountpoint(basePath); + return true; +} + +void LITTLEFSFS::end() +{ + if(esp_littlefs_mounted(LFS_NAME)){ + esp_err_t err = esp_vfs_littlefs_unregister(LFS_NAME); + if(err){ + log_e("Unmounting LITTLEFS failed! Error: %d", err); + return; + } + _impl->mountpoint(NULL); + } +} + +bool LITTLEFSFS::format() +{ + disableCore0WDT(); + esp_err_t err = esp_littlefs_format(LFS_NAME); + enableCore0WDT(); + if(err){ + log_e("Formatting LITTLEFS failed! Error: %d", err); + return false; + } + return true; +} + +size_t LITTLEFSFS::totalBytes() +{ + size_t total,used; + if(esp_littlefs_info(LFS_NAME, &total, &used)){ + return 0; + } + return total; +} + +size_t LITTLEFSFS::usedBytes() +{ + size_t total,used; + if(esp_littlefs_info(LFS_NAME, &total, &used)){ + return 0; + } + return used; +} + +LITTLEFSFS LITTLEFS; + diff --git a/lib/LITTLEFS/src/LITTLEFS.h b/lib/LITTLEFS/src/LITTLEFS.h new file mode 100644 index 00000000..fbd6f09e --- /dev/null +++ b/lib/LITTLEFS/src/LITTLEFS.h @@ -0,0 +1,38 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// 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. +#ifndef _LITTLEFS_H_ +#define _LITTLEFS_H_ + +#include "FS.h" + +namespace fs +{ + +class LITTLEFSFS : public FS +{ +public: + LITTLEFSFS(); + bool begin(bool formatOnFail=false, const char * basePath="/littlefs", uint8_t maxOpenFiles=5); + bool format(); + size_t totalBytes(); + size_t usedBytes(); + void end(); +}; + +} + +extern fs::LITTLEFSFS LITTLEFS; + + +#endif diff --git a/lib/LITTLEFS/src/esp_littlefs.c b/lib/LITTLEFS/src/esp_littlefs.c new file mode 100644 index 00000000..352cfa57 --- /dev/null +++ b/lib/LITTLEFS/src/esp_littlefs.c @@ -0,0 +1,1481 @@ +/** + * @file esp_littlefs.c + * @brief Maps LittleFS <-> ESP_VFS + * @author Brian Pugh + */ + +//#define LOG_LOCAL_LEVEL 4 + +#include "esp_littlefs.h" + +#include +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "esp_spi_flash.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "littlefs_api.h" +#include "rom/spi_flash.h" + +static const char TAG[] = "esp_littlefs"; + +#define CONFIG_LITTLEFS_BLOCK_SIZE 4096 /* ESP32 can only operate at 4kb */ + +/* File Descriptor Caching Params */ +#define CONFIG_LITTLEFS_FD_CACHE_REALLOC_FACTOR 2 /* Amount to resize FD cache by */ +#define CONFIG_LITTLEFS_FD_CACHE_MIN_SIZE 4 /* Minimum size of FD cache */ +#define CONFIG_LITTLEFS_FD_CACHE_HYST 4 /* When shrinking, leave this many trailing FD slots available */ + +#define CONFIG_LITTLEFS_MAX_PARTITIONS 3 +#define CONFIG_LITTLEFS_PAGE_SIZE 256 +#define CONFIG_LITTLEFS_OBJ_NAME_LEN 64 +#define CONFIG_LITTLEFS_READ_SIZE 128 +#define CONFIG_LITTLEFS_WRITE_SIZE 128 +#define CONFIG_LITTLEFS_LOOKAHEAD_SIZE 128 +#define CONFIG_LITTLEFS_CACHE_SIZE 128 +#define CONFIG_LITTLEFS_BLOCK_CYCLES 512 +#define CONFIG_LITTLEFS_USE_MTIME 0 +#define CONFIG_LITTLEFS_MTIME_USE_SECONDS 1 + +/** + * @brief littlefs DIR structure + */ +typedef struct { + DIR dir; /*!< VFS DIR struct */ + lfs_dir_t d; /*!< littlefs DIR struct */ + struct dirent e; /*!< Last open dirent */ + long offset; /*!< Offset of the current dirent */ + char *path; /*!< Requested directory name */ +} vfs_littlefs_dir_t; + +static int vfs_littlefs_open(void *ctx, const char *path, int flags, int mode); +static ssize_t vfs_littlefs_write(void *ctx, int fd, const void *data, size_t size); +static ssize_t vfs_littlefs_read(void *ctx, int fd, void *dst, size_t size); +static int vfs_littlefs_close(void *ctx, int fd); +static off_t vfs_littlefs_lseek(void *ctx, int fd, off_t offset, int mode); +static int vfs_littlefs_stat(void *ctx, const char *path, struct stat *st); +static int vfs_littlefs_unlink(void *ctx, const char *path); +static int vfs_littlefs_rename(void *ctx, const char *src, const char *dst); +static DIR *vfs_littlefs_opendir(void *ctx, const char *name); +static int vfs_littlefs_closedir(void *ctx, DIR *pdir); +static struct dirent *vfs_littlefs_readdir(void *ctx, DIR *pdir); +static int vfs_littlefs_readdir_r(void *ctx, DIR *pdir, + struct dirent *entry, struct dirent **out_dirent); +static long vfs_littlefs_telldir(void *ctx, DIR *pdir); +static void vfs_littlefs_seekdir(void *ctx, DIR *pdir, long offset); +static int vfs_littlefs_mkdir(void *ctx, const char *name, mode_t mode); +static int vfs_littlefs_rmdir(void *ctx, const char *name); +static int vfs_littlefs_fsync(void *ctx, int fd); + +static esp_err_t esp_littlefs_init(const esp_vfs_littlefs_conf_t *conf); +static esp_err_t esp_littlefs_erase_partition(const char *partition_label); +static esp_err_t esp_littlefs_by_label(const char *label, int *index); +static esp_err_t esp_littlefs_get_empty(int *index); +static void esp_littlefs_free(esp_littlefs_t **efs); +static void esp_littlefs_dir_free(vfs_littlefs_dir_t *dir); +static int esp_littlefs_flags_conv(int m); +#if CONFIG_LITTLEFS_USE_MTIME +static int vfs_littlefs_utime(void *ctx, const char *path, const struct utimbuf *times); +static void vfs_littlefs_update_mtime(esp_littlefs_t *efs, const char *path); +static int vfs_littlefs_update_mtime_value(esp_littlefs_t *efs, const char *path, time_t t); +static time_t vfs_littlefs_get_mtime(esp_littlefs_t *efs, const char *path); +#endif + +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH +/* The only way in LittleFS to get info is via a path (lfs_stat), so it cannot + * be done if the path isn't stored. */ +static int vfs_littlefs_fstat(void *ctx, int fd, struct stat *st); +#endif + +static int sem_take(esp_littlefs_t *efs); +static int sem_give(esp_littlefs_t *efs); + +static SemaphoreHandle_t _efs_lock = NULL; +static esp_littlefs_t *_efs[CONFIG_LITTLEFS_MAX_PARTITIONS] = {0}; + +/******************** + * Helper Functions * + ********************/ +void esp_littlefs_free_fds(esp_littlefs_t *efs) { + /* Need to free all files that were opened */ + while (efs->file) { + vfs_littlefs_file_t *next = efs->file->next; + free(efs->file); + efs->file = next; + } + free(efs->cache); + efs->cache = 0; + efs->cache_size = efs->fd_count = 0; +} + +/******************** + * Public Functions * + ********************/ + +bool esp_littlefs_mounted(const char *partition_label) { + int index; + esp_err_t err; + + err = esp_littlefs_by_label(partition_label, &index); + if (err != ESP_OK) return false; + return _efs[index]->cache_size > 0; +} + +esp_err_t esp_littlefs_info(const char *partition_label, size_t *total_bytes, size_t *used_bytes) { + int index; + esp_err_t err; + esp_littlefs_t *efs = NULL; + + err = esp_littlefs_by_label(partition_label, &index); + if (err != ESP_OK) return false; + efs = _efs[index]; + + if (total_bytes) *total_bytes = efs->cfg.block_size * efs->cfg.block_count; + if (used_bytes) *used_bytes = efs->cfg.block_size * lfs_fs_size(efs->fs); + + return ESP_OK; +} + +esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t *conf) { + assert(conf->base_path); + const esp_vfs_t vfs = { + .flags = ESP_VFS_FLAG_CONTEXT_PTR, + .write_p = &vfs_littlefs_write, + .lseek_p = &vfs_littlefs_lseek, + .read_p = &vfs_littlefs_read, + .open_p = &vfs_littlefs_open, + .close_p = &vfs_littlefs_close, +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + .fstat_p = &vfs_littlefs_fstat, +#else + .fstat_p = NULL, /* Not supported */ +#endif + .stat_p = &vfs_littlefs_stat, + .link_p = NULL, /* Not Supported */ + .unlink_p = &vfs_littlefs_unlink, + .rename_p = &vfs_littlefs_rename, + .opendir_p = &vfs_littlefs_opendir, + .closedir_p = &vfs_littlefs_closedir, + .readdir_p = &vfs_littlefs_readdir, + .readdir_r_p = &vfs_littlefs_readdir_r, + .seekdir_p = &vfs_littlefs_seekdir, + .telldir_p = &vfs_littlefs_telldir, + .mkdir_p = &vfs_littlefs_mkdir, + .rmdir_p = &vfs_littlefs_rmdir, + .fsync_p = &vfs_littlefs_fsync, +#if CONFIG_LITTLEFS_USE_MTIME + .utime_p = &vfs_littlefs_utime, +#endif // CONFIG_LITTLEFS_USE_MTIME + }; + + esp_err_t err = esp_littlefs_init(conf); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize LittleFS"); + return err; + } + + int index; + if (esp_littlefs_by_label(conf->partition_label, &index) != ESP_OK) { + ESP_LOGE(TAG, "Unable to find partition \"%s\"", conf->partition_label); + return ESP_ERR_NOT_FOUND; + } + + strlcat(_efs[index]->base_path, conf->base_path, ESP_VFS_PATH_MAX + 1); + err = esp_vfs_register(conf->base_path, &vfs, _efs[index]); + if (err != ESP_OK) { + esp_littlefs_free(&_efs[index]); + ESP_LOGE(TAG, "Failed to register Littlefs to \"%s\"", conf->base_path); + return err; + } + + ESP_LOGD(TAG, "Successfully registered LittleFS to \"%s\"", conf->base_path); + return ESP_OK; +} + +esp_err_t esp_vfs_littlefs_unregister(const char *partition_label) { + assert(partition_label); + int index; + if (esp_littlefs_by_label(partition_label, &index) != ESP_OK) { + ESP_LOGE(TAG, "Partition was never registered."); + return ESP_ERR_INVALID_STATE; + } + ESP_LOGD(TAG, "Unregistering \"%s\"", partition_label); + esp_err_t err = esp_vfs_unregister(_efs[index]->base_path); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to unregister \"%s\"", partition_label); + return err; + } + esp_littlefs_free(&_efs[index]); + _efs[index] = NULL; + return ESP_OK; +} + +esp_err_t esp_littlefs_format(const char *partition_label) { + assert(partition_label); + + bool was_mounted = false; + bool efs_free = false; + int index = -1; + esp_err_t err; + esp_littlefs_t *efs = NULL; + + ESP_LOGI(TAG, "Formatting \"%s\"", partition_label); + + /* Get a context */ + err = esp_littlefs_by_label(partition_label, &index); + + if (err != ESP_OK) { + /* Create a tmp context */ + ESP_LOGD(TAG, "Temporarily creating EFS context."); + efs_free = true; + const esp_vfs_littlefs_conf_t conf = { + /* base_name not necessary for initializing */ + .dont_mount = true, + .partition_label = partition_label, + }; + err = esp_littlefs_init(&conf); /* Internally MIGHT call esp_littlefs_format */ + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize to format."); + goto exit; + } + + err = esp_littlefs_by_label(partition_label, &index); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Error obtaining context."); + goto exit; + } + } + + efs = _efs[index]; + assert(efs); + + /* Unmount if mounted */ + if (efs->cache_size > 0) { + int res; + ESP_LOGD(TAG, "Partition was mounted. Unmounting..."); + was_mounted = true; + res = lfs_unmount(efs->fs); + if (res != LFS_ERR_OK) { + ESP_LOGE(TAG, "Failed to unmount."); + return ESP_FAIL; + } + esp_littlefs_free_fds(efs); + } + + /* Erase and Format */ + { + int res; + ESP_LOGD(TAG, "Formatting filesystem"); + esp_littlefs_erase_partition(partition_label); + res = lfs_format(efs->fs, &efs->cfg); + if (res != LFS_ERR_OK) { + ESP_LOGE(TAG, "Failed to format filesystem"); + return ESP_FAIL; + } + } + + /* Mount filesystem */ + if (was_mounted) { + int res; + /* Remount the partition */ + ESP_LOGD(TAG, "Remounting formatted partition"); + res = lfs_mount(efs->fs, &efs->cfg); + if (res != LFS_ERR_OK) { + ESP_LOGE(TAG, "Failed to re-mount filesystem"); + return ESP_FAIL; + } + efs->cache_size = CONFIG_LITTLEFS_FD_CACHE_MIN_SIZE; // Initial size of cache; will resize ondemand + efs->cache = calloc(sizeof(*efs->cache), efs->cache_size); + } + ESP_LOGD(TAG, "Format Success!"); + + err = ESP_OK; + +exit: + if (efs_free && index >= 0) esp_littlefs_free(&_efs[index]); + return err; +} + +#if CONFIG_LITTLEFS_HUMAN_READABLE +/** + * @brief converts an enumerated lfs error into a string. + * @param lfs_error The littlefs error. + */ +const char *esp_littlefs_errno(enum lfs_error lfs_errno) { + switch (lfs_errno) { + case LFS_ERR_OK: + return "LFS_ERR_OK"; + case LFS_ERR_IO: + return "LFS_ERR_IO"; + case LFS_ERR_CORRUPT: + return "LFS_ERR_CORRUPT"; + case LFS_ERR_NOENT: + return "LFS_ERR_NOENT"; + case LFS_ERR_EXIST: + return "LFS_ERR_EXIST"; + case LFS_ERR_NOTDIR: + return "LFS_ERR_NOTDIR"; + case LFS_ERR_ISDIR: + return "LFS_ERR_ISDIR"; + case LFS_ERR_NOTEMPTY: + return "LFS_ERR_NOTEMPTY"; + case LFS_ERR_BADF: + return "LFS_ERR_BADF"; + case LFS_ERR_FBIG: + return "LFS_ERR_FBIG"; + case LFS_ERR_INVAL: + return "LFS_ERR_INVAL"; + case LFS_ERR_NOSPC: + return "LFS_ERR_NOSPC"; + case LFS_ERR_NOMEM: + return "LFS_ERR_NOMEM"; + case LFS_ERR_NOATTR: + return "LFS_ERR_NOATTR"; + case LFS_ERR_NAMETOOLONG: + return "LFS_ERR_NAMETOOLONG"; + default: + return "LFS_ERR_UNDEFINED"; + } + return ""; +} +#else +#define esp_littlefs_errno(x) "" +#endif + +/******************** + * Static Functions * + ********************/ + +/*** Helpers ***/ + +/** + * @brief Free and clear a littlefs definition structure. + * @param efs Pointer to pointer to struct. Done this way so we can also zero + * out the pointer. + */ +static void esp_littlefs_free(esp_littlefs_t **efs) { + esp_littlefs_t *e = *efs; + if (e == NULL) return; + *efs = NULL; + + if (e->fs) { + if (e->cache_size > 0) lfs_unmount(e->fs); + free(e->fs); + } + if (e->lock) vSemaphoreDelete(e->lock); + esp_littlefs_free_fds(e); + free(e); +} + +/** + * @brief Free a vfs_littlefs_dir_t struct. + */ +static void esp_littlefs_dir_free(vfs_littlefs_dir_t *dir) { + if (dir == NULL) return; + if (dir->path) free(dir->path); + free(dir); +} + +/** + * Get a mounted littlefs filesystem by label. + * @param[in] label + * @param[out] index index into _efs + * @return ESP_OK on success + */ +static esp_err_t esp_littlefs_by_label(const char *label, int *index) { + int i; + esp_littlefs_t *p; + + if (!label || !index) return ESP_ERR_INVALID_ARG; + + ESP_LOGD(TAG, "Searching for existing filesystem for partition \"%s\"", label); + + for (i = 0; i < CONFIG_LITTLEFS_MAX_PARTITIONS; i++) { + p = _efs[i]; + if (p) { + if (strncmp(label, p->partition->label, 17) == 0) { + *index = i; + ESP_LOGD(TAG, "Found existing filesystem \"%s\" at index %d", label, *index); + return ESP_OK; + } + } + } + + ESP_LOGD(TAG, "Existing filesystem \%s\" not found", label); + return ESP_ERR_NOT_FOUND; +} + +/** + * @brief Get the index of an unallocated LittleFS slot. + * @param[out] index Indexd of free LittleFS slot + * @return ESP_OK on success + */ +static esp_err_t esp_littlefs_get_empty(int *index) { + assert(index); + for (uint8_t i = 0; i < CONFIG_LITTLEFS_MAX_PARTITIONS; i++) { + if (_efs[i] == NULL) { + *index = i; + return ESP_OK; + } + } + ESP_LOGE(TAG, "No more free partitions available."); + return ESP_FAIL; +} + +/** + * @brief erase a partition; make sure LittleFS is unmounted first. + * @param partition_label NULL-terminated string of partition to erase + * @return ESP_OK on success + */ +static esp_err_t esp_littlefs_erase_partition(const char *partition_label) { + ESP_LOGD(TAG, "Erasing partition..."); + + const esp_partition_t *partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, + partition_label); + if (!partition) { + ESP_LOGE(TAG, "partition \"%s\" could not be found", partition_label); + return ESP_ERR_NOT_FOUND; + } + + if (esp_partition_erase_range(partition, 0, partition->size) != ESP_OK) { + ESP_LOGE(TAG, "Failed to erase partition"); + return ESP_FAIL; + } + + return ESP_OK; +} + +/** + * @brief Convert fcntl flags to littlefs flags + * @param m fcntl flags + * @return lfs flags + */ +static int esp_littlefs_flags_conv(int m) { + int lfs_flags = 0; + if (m == O_APPEND) lfs_flags |= LFS_O_APPEND; + if (m == O_RDONLY) lfs_flags |= LFS_O_RDONLY; + if (m & O_WRONLY) lfs_flags |= LFS_O_WRONLY; + if (m & O_RDWR) lfs_flags |= LFS_O_RDWR; + if (m & O_EXCL) lfs_flags |= LFS_O_EXCL; + if (m & O_CREAT) lfs_flags |= LFS_O_CREAT; + if (m & O_TRUNC) lfs_flags |= LFS_O_TRUNC; + return lfs_flags; +} + +/** + * @brief Initialize and mount littlefs + * @param[in] conf Filesystem Configuration + * @return ESP_OK on success + */ +static esp_err_t esp_littlefs_init(const esp_vfs_littlefs_conf_t *conf) { + int index = -1; + esp_err_t err = ESP_FAIL; + const esp_partition_t *partition = NULL; + esp_littlefs_t *efs = NULL; + + if (_efs_lock == NULL) { + static portMUX_TYPE mux = portMUX_INITIALIZER_UNLOCKED; + portENTER_CRITICAL(&mux); + if (_efs_lock == NULL) { + _efs_lock = xSemaphoreCreateMutex(); + assert(_efs_lock); + } + portEXIT_CRITICAL(&mux); + } + + xSemaphoreTake(_efs_lock, portMAX_DELAY); + + if (esp_littlefs_get_empty(&index) != ESP_OK) { + ESP_LOGE(TAG, "max mounted partitions reached"); + err = ESP_ERR_INVALID_STATE; + goto exit; + } + + /* Input and Environment Validation */ + if (esp_littlefs_by_label(conf->partition_label, &index) == ESP_OK) { + ESP_LOGE(TAG, "Partition already used"); + err = ESP_ERR_INVALID_STATE; + goto exit; + } + + { + uint32_t flash_page_size = g_rom_flashchip.page_size; + uint32_t log_page_size = CONFIG_LITTLEFS_PAGE_SIZE; + if (log_page_size % flash_page_size != 0) { + ESP_LOGE(TAG, "LITTLEFS_PAGE_SIZE is not multiple of flash chip page size (%d)", + flash_page_size); + err = ESP_ERR_INVALID_ARG; + goto exit; + } + } + + if (NULL == conf->partition_label) { + ESP_LOGE(TAG, "Partition label must be provided."); + err = ESP_ERR_INVALID_ARG; + goto exit; + } + + partition = esp_partition_find_first( + ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, + conf->partition_label); + + if (!partition) { + ESP_LOGE(TAG, "partition \"%s\" could not be found", conf->partition_label); + err = ESP_ERR_NOT_FOUND; + goto exit; + } + + if (partition->encrypted) { + // TODO: allow encryption; should probably be fine, + // just not allowing until tested. + ESP_LOGE(TAG, "littlefs can not run on encrypted partition"); + err = ESP_ERR_INVALID_STATE; + goto exit; + } + + /* Allocate Context */ + efs = calloc(1, sizeof(esp_littlefs_t)); + if (efs == NULL) { + ESP_LOGE(TAG, "esp_littlefs could not be malloced"); + err = ESP_ERR_NO_MEM; + goto exit; + } + efs->partition = partition; + + { /* LittleFS Configuration */ + efs->cfg.context = efs; + + // block device operations + efs->cfg.read = littlefs_api_read; + efs->cfg.prog = littlefs_api_prog; + efs->cfg.erase = littlefs_api_erase; + efs->cfg.sync = littlefs_api_sync; + + // block device configuration + efs->cfg.read_size = CONFIG_LITTLEFS_READ_SIZE; + efs->cfg.prog_size = CONFIG_LITTLEFS_WRITE_SIZE; + efs->cfg.block_size = CONFIG_LITTLEFS_BLOCK_SIZE; + ; + efs->cfg.block_count = efs->partition->size / efs->cfg.block_size; + efs->cfg.cache_size = CONFIG_LITTLEFS_CACHE_SIZE; + efs->cfg.lookahead_size = CONFIG_LITTLEFS_LOOKAHEAD_SIZE; + efs->cfg.block_cycles = CONFIG_LITTLEFS_BLOCK_CYCLES; + } + + efs->lock = xSemaphoreCreateRecursiveMutex(); + if (efs->lock == NULL) { + ESP_LOGE(TAG, "mutex lock could not be created"); + err = ESP_ERR_NO_MEM; + goto exit; + } + + efs->fs = calloc(1, sizeof(lfs_t)); + if (efs->fs == NULL) { + ESP_LOGE(TAG, "littlefs could not be malloced"); + err = ESP_ERR_NO_MEM; + goto exit; + } + + // Mount and Error Check + _efs[index] = efs; + if (!conf->dont_mount) { + int res = lfs_mount(efs->fs, &efs->cfg); + + if (conf->format_if_mount_failed && res != LFS_ERR_OK) { + esp_err_t err; + ESP_LOGW(TAG, "mount failed, %s (%i). formatting...", esp_littlefs_errno(res), res); + err = esp_littlefs_format(efs->partition->label); + if (err != ESP_OK) { + ESP_LOGE(TAG, "format failed"); + err = ESP_FAIL; + goto exit; + } + res = lfs_mount(efs->fs, &efs->cfg); + } + if (res != LFS_ERR_OK) { + ESP_LOGE(TAG, "mount failed, %s (%i)", esp_littlefs_errno(res), res); + err = ESP_FAIL; + goto exit; + } + efs->cache_size = 4; + efs->cache = calloc(sizeof(*efs->cache), efs->cache_size); + } + + err = ESP_OK; + +exit: + if (err != ESP_OK) { + if (index >= 0) { + esp_littlefs_free(&_efs[index]); + } else { + esp_littlefs_free(&efs); + } + } + xSemaphoreGive(_efs_lock); + return err; +} + +/** + * @brief + * @parameter efs file system context + */ +static inline int sem_take(esp_littlefs_t *efs) { + int res; +#if LOG_LOCAL_LEVEL >= 4 + ESP_LOGD(TAG, "------------------------ Sem Taking [%s]", pcTaskGetTaskName(NULL)); +#endif + res = xSemaphoreTakeRecursive(efs->lock, portMAX_DELAY); +#if LOG_LOCAL_LEVEL >= 4 + ESP_LOGD(TAG, "--------------------->>> Sem Taken [%s]", pcTaskGetTaskName(NULL)); +#endif + return res; +} + +/** + * @brief + * @parameter efs file system context + */ +static inline int sem_give(esp_littlefs_t *efs) { +#if LOG_LOCAL_LEVEL >= 4 + ESP_LOGD(TAG, "---------------------<<< Sem Give [%s]", pcTaskGetTaskName(NULL)); +#endif + return xSemaphoreGiveRecursive(efs->lock); +} + +/* We are using a double allocation system here, which an array and a linked list. + The array contains the pointer to the file descriptor (the index in the array is what's returned to the user). + The linked list is used for file descriptors. + This means that position of nodes in the list must stay consistent: + - Allocation is obvious (append to the list from the head, and realloc the pointers array) + There is still a O(N) search in the cache for a free position to store + - Searching is a O(1) process (good) + - Deallocation is more tricky. That is, for example, + if you need to remove node 5 in a 12 nodes list, you'll have to: + 1) Mark the 5th position as freed (if it's the last position of the array realloc smaller) + 2) Walk the list until finding the pointer to the node O(N) and scrub the node so the chained list stays consistent + 3) Deallocate the node +*/ + +/** + * @brief Get a file descriptor + * @param[in,out] efs file system context + * @param[out] file pointer to a file that'll be filled with a file object + * @param[in] path_len the length of the filepath in bytes (including terminating zero byte) + * @return integer file descriptor. Returns -1 if a FD cannot be obtained. + * @warning This must be called with lock taken + */ +static int esp_littlefs_allocate_fd(esp_littlefs_t *efs, vfs_littlefs_file_t **file +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + , + const size_t path_len +#endif +) { + int i = -1; + + assert(efs->fd_count < UINT16_MAX); + assert(efs->cache_size < UINT16_MAX); + + /* Make sure there is enough space in the cache to store new fd */ + if (efs->fd_count + 1 > efs->cache_size) { + uint16_t new_size = (uint16_t)MIN(UINT16_MAX, CONFIG_LITTLEFS_FD_CACHE_REALLOC_FACTOR * efs->cache_size); + /* Resize the cache */ + vfs_littlefs_file_t **new_cache = realloc(efs->cache, new_size * sizeof(*efs->cache)); + if (!new_cache) { + ESP_LOGE(TAG, "Unable to allocate file cache"); + return -1; /* If it fails here, no harm is done to the filesystem, so it's safe */ + } + /* Zero out the new portions of the cache */ + memset(&new_cache[efs->cache_size], 0, (new_size - efs->cache_size) * sizeof(*efs->cache)); + efs->cache = new_cache; + efs->cache_size = new_size; + } + + /* Allocate file descriptor here now */ +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + *file = calloc(1, sizeof(**file) + path_len); +#else + *file = calloc(1, sizeof(**file)); +#endif + + if (*file == NULL) { + /* If it fails here, the file system might have a larger cache, but it's harmless, no need to reverse it */ + ESP_LOGE(TAG, "Unable to allocate FD"); + return -1; + } + + /* Starting from here, nothing can fail anymore */ + +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + /* The trick here is to avoid dual allocation so the path pointer + should point to the next byte after it: + file => [ lfs_file | # | next | path | free_space ] + | /\ + |__/ + */ + (*file)->path = (char *)(*file) + sizeof(**file); +#endif + + /* Now find a free place in cache */ + for (i = 0; i < efs->cache_size; i++) { + if (efs->cache[i] == NULL) { + efs->cache[i] = *file; + break; + } + } + /* Save file in the list */ + (*file)->next = efs->file; + efs->file = *file; + efs->fd_count++; + return i; +} + +/** + * @brief Release a file descriptor + * @param[in,out] efs file system context + * @param[in] fd File Descriptor to release + * @return 0 on success. -1 if a FD cannot be obtained. + * @warning This must be called with lock taken + */ +static int esp_littlefs_free_fd(esp_littlefs_t *efs, int fd) { + vfs_littlefs_file_t *file, *head; + + if ((uint32_t)fd >= efs->cache_size) { + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return -1; + } + + /* Get the file descriptor to free it */ + file = efs->cache[fd]; + head = efs->file; + /* Search for file in SLL to remove it */ + if (file == head) { + /* Last file, can't fail */ + efs->file = efs->file->next; + } else { + while (head && head->next != file) { + head = head->next; + } + if (!head) { + ESP_LOGE(TAG, "Inconsistent list"); + return -1; + } + /* Transaction starts here and can't fail anymore */ + head->next = file->next; + } + efs->cache[fd] = NULL; + efs->fd_count--; + + ESP_LOGD(TAG, "Clearing FD"); + free(file); + +#if 0 + /* Realloc smaller if its possible + * * Find and realloc based on number of trailing NULL ptrs in cache + * * Leave some hysteris to prevent thrashing around resize points + * This is disabled for now because it adds unnecessary complexity + * and binary size increase that outweights its ebenfits. + */ + if(efs->cache_size > CONFIG_LITTLEFS_FD_CACHE_MIN_SIZE) { + uint16_t n_free; + uint16_t new_size = efs->cache_size / CONFIG_LITTLEFS_FD_CACHE_REALLOC_FACTOR; + + if(new_size >= CONFIG_LITTLEFS_FD_CACHE_MIN_SIZE) { + /* Count number of trailing NULL ptrs */ + for(n_free=0; n_free < efs->cache_size; n_free++) { + if(efs->cache[efs->cache_size - n_free - 1] != NULL) { + break; + } + } + + if(n_free >= (efs->cache_size - new_size)){ + new_size += CONFIG_LITTLEFS_FD_CACHE_HYST; + ESP_LOGD(TAG, "Reallocating cache %i -> %i", efs->cache_size, new_size); + vfs_littlefs_file_t ** new_cache; + new_cache = realloc(efs->cache, new_size * sizeof(*efs->cache)); + /* No harm on realloc failure, continue using the oversized cache */ + if(new_cache) { + efs->cache = new_cache; + efs->cache_size = new_size; + } + } + } + } +#endif + + return 0; +} + +/** + * @brief Compute the 32bit DJB2 hash of the given string. + * @param[in] path the path to hash + * @returns the hash for this path + */ +static uint32_t compute_hash(const char *path) { + uint32_t hash = 5381; + char c; + + while ((c = *path++)) + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + return hash; +} + +/** + * @brief finds an open file descriptor by file name. + * @param[in,out] efs file system context + * @param[in] path File path to check. + * @returns integer file descriptor. Returns -1 if not found. + * @warning This must be called with lock taken + * @warning if CONFIG_LITTLEFS_USE_ONLY_HASH, there is a slim chance an + * erroneous FD may be returned on hash collision. + */ +static int esp_littlefs_get_fd_by_name(esp_littlefs_t *efs, const char *path) { + uint32_t hash = compute_hash(path); + + for (uint16_t i = 0, j = 0; i < efs->cache_size && j < efs->fd_count; i++) { + if (efs->cache[i]) { + ++j; + + if ( + efs->cache[i]->hash == hash // Faster than strcmp +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + && strcmp(path, efs->cache[i]->path) == 0 // May as well check incase of hash collision. Usually short-circuited. +#endif + ) { + ESP_LOGD(TAG, "Found \"%s\" at FD %d.", path, i); + return i; + } + } + } + ESP_LOGD(TAG, "Unable to get a find FD for \"%s\"", path); + return -1; +} + +/*** Filesystem Hooks ***/ + +static int vfs_littlefs_open(void *ctx, const char *path, int flags, int mode) { + /* Note: mode is currently unused */ + int fd = -1, lfs_flags, res; + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + vfs_littlefs_file_t *file = NULL; +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + size_t path_len = strlen(path) + 1; // include NULL terminator +#endif + assert(path); + + ESP_LOGD(TAG, "Opening %s", path); + + /* Convert flags to lfs flags */ + lfs_flags = esp_littlefs_flags_conv(flags); + + /* Get a FD */ + sem_take(efs); + fd = esp_littlefs_allocate_fd(efs, &file +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + , + path_len +#endif + ); + if (fd < 0) { + sem_give(efs); + ESP_LOGE(TAG, "Error obtaining FD"); + return LFS_ERR_INVAL; + } + /* Open File */ + res = lfs_file_open(efs->fs, &file->file, path, lfs_flags); + + if (res < 0) { + esp_littlefs_free_fd(efs, fd); + sem_give(efs); + ESP_LOGE(TAG, "Failed to open file. Error %s (%d)", + esp_littlefs_errno(res), res); + return LFS_ERR_INVAL; + } + + file->hash = compute_hash(path); +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + memcpy(file->path, path, path_len); +#endif + +#if CONFIG_LITTLEFS_USE_MTIME + if (!(lfs_flags & LFS_O_RDONLY)) { + /* If this is being opened as not read-only */ + vfs_littlefs_update_mtime(efs, path); + } +#endif + + sem_give(efs); + ESP_LOGD(TAG, "Done opening %s", path); + return fd; +} + +static ssize_t vfs_littlefs_write(void *ctx, int fd, const void *data, size_t size) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + ssize_t res; + vfs_littlefs_file_t *file = NULL; + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_file_write(efs->fs, &file->file, data, size); + sem_give(efs); + + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to write FD %d; path \"%s\". Error %s (%d)", + fd, file->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to write FD %d. Error %s (%d)", + fd, esp_littlefs_errno(res), res); +#endif + return res; + } + + return res; +} + +static ssize_t vfs_littlefs_read(void *ctx, int fd, void *dst, size_t size) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + ssize_t res; + vfs_littlefs_file_t *file = NULL; + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_file_read(efs->fs, &file->file, dst, size); + sem_give(efs); + + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to read file \"%s\". Error %s (%d)", + file->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to read FD %d. Error %s (%d)", + fd, esp_littlefs_errno(res), res); +#endif + return res; + } + + return res; +} + +static int vfs_littlefs_close(void *ctx, int fd) { + // TODO update mtime on close? SPIFFS doesn't do this + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + int res; + vfs_littlefs_file_t *file = NULL; + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_file_close(efs->fs, &file->file); + if (res < 0) { + sem_give(efs); +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to close file \"%s\". Error %s (%d)", + file->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to close Fd %d. Error %s (%d)", + fd, esp_littlefs_errno(res), res); +#endif + return res; + } + esp_littlefs_free_fd(efs, fd); + sem_give(efs); + return res; +} + +static off_t vfs_littlefs_lseek(void *ctx, int fd, off_t offset, int mode) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + lfs_soff_t res; + vfs_littlefs_file_t *file = NULL; + int whence; + + switch (mode) { + case SEEK_SET: + whence = LFS_SEEK_SET; + break; + case SEEK_CUR: + whence = LFS_SEEK_CUR; + break; + case SEEK_END: + whence = LFS_SEEK_END; + break; + default: + ESP_LOGE(TAG, "Invalid mode"); + return -1; + } + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_file_seek(efs->fs, &file->file, offset, whence); + sem_give(efs); + + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to seek file \"%s\" to offset %08x. Error %s (%d)", + file->path, (unsigned int)offset, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to seek FD %d to offset %08x. Error (%d)", + fd, (unsigned int)offset, res); +#endif + return res; + } + + return res; +} + +static int vfs_littlefs_fsync(void *ctx, int fd) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + ssize_t res; + vfs_littlefs_file_t *file = NULL; + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD %d must be <%d.", fd, efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_file_sync(efs->fs, &file->file); + sem_give(efs); + + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to sync file \"%s\". Error %s (%d)", + file->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to sync file %d. Error %d", fd, res); +#endif + return res; + } + + return res; +} + +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH +static int vfs_littlefs_fstat(void *ctx, int fd, struct stat *st) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + struct lfs_info info; + int res; + vfs_littlefs_file_t *file = NULL; + + memset(st, 0, sizeof(struct stat)); + st->st_blksize = efs->cfg.block_size; + + sem_take(efs); + if ((uint32_t)fd > efs->cache_size) { + sem_give(efs); + ESP_LOGE(TAG, "FD must be <%d.", efs->cache_size); + return LFS_ERR_BADF; + } + file = efs->cache[fd]; + res = lfs_stat(efs->fs, file->path, &info); + if (res < 0) { + sem_give(efs); + ESP_LOGE(TAG, "Failed to stat file \"%s\". Error %s (%d)", + file->path, esp_littlefs_errno(res), res); + return res; + } + +#if CONFIG_LITTLEFS_USE_MTIME + st->st_mtime = vfs_littlefs_get_mtime(efs, file->path); +#endif + + sem_give(efs); + + st->st_size = info.size; + st->st_mode = ((info.type == LFS_TYPE_REG) ? S_IFREG : S_IFDIR); + return 0; +} +#endif + +static int vfs_littlefs_stat(void *ctx, const char *path, struct stat *st) { + assert(path); + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + struct lfs_info info; + int res; + + memset(st, 0, sizeof(struct stat)); + st->st_blksize = efs->cfg.block_size; + + sem_take(efs); + res = lfs_stat(efs->fs, path, &info); + if (res < 0) { + sem_give(efs); + /* Not strictly an error, since stat can be used to check + * if a file exists */ + ESP_LOGI(TAG, "Failed to stat path \"%s\". Error %s (%d)", + path, esp_littlefs_errno(res), res); + return res; + } +#if CONFIG_LITTLEFS_USE_MTIME + st->st_mtime = vfs_littlefs_get_mtime(efs, path); +#endif + sem_give(efs); + st->st_size = info.size; + st->st_mode = ((info.type == LFS_TYPE_REG) ? S_IFREG : S_IFDIR); + return 0; +} + +static int vfs_littlefs_unlink(void *ctx, const char *path) { +#define fail_str_1 "Failed to unlink path \"%s\"." + assert(path); + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + struct lfs_info info; + int res; + + sem_take(efs); + res = lfs_stat(efs->fs, path, &info); + if (res < 0) { + sem_give(efs); + ESP_LOGE(TAG, fail_str_1 " Error %s (%d)", + path, esp_littlefs_errno(res), res); + return res; + } + + if (esp_littlefs_get_fd_by_name(efs, path) >= 0) { + sem_give(efs); + ESP_LOGE(TAG, fail_str_1 " Has open FD.", path); + return -1; + } + + if (info.type == LFS_TYPE_DIR) { + sem_give(efs); + ESP_LOGE(TAG, "Cannot unlink a directory."); + return LFS_ERR_ISDIR; + } + + res = lfs_remove(efs->fs, path); + if (res < 0) { + sem_give(efs); + ESP_LOGE(TAG, fail_str_1 " Error %s (%d)", + path, esp_littlefs_errno(res), res); + return res; + } + + sem_give(efs); + + return 0; +#undef fail_str_1 +} + +static int vfs_littlefs_rename(void *ctx, const char *src, const char *dst) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + int res; + + sem_take(efs); + + if (esp_littlefs_get_fd_by_name(efs, src) >= 0) { + sem_give(efs); + ESP_LOGE(TAG, "Cannot rename; src \"%s\" is open.", src); + return -1; + } else if (esp_littlefs_get_fd_by_name(efs, dst) >= 0) { + sem_give(efs); + ESP_LOGE(TAG, "Cannot rename; dst \"%s\" is open.", dst); + return -1; + } + + res = lfs_rename(efs->fs, src, dst); + sem_give(efs); + if (res < 0) { + ESP_LOGE(TAG, "Failed to rename \"%s\" -> \"%s\". Error %s (%d)", + src, dst, esp_littlefs_errno(res), res); + return res; + } + + return 0; +} + +static DIR *vfs_littlefs_opendir(void *ctx, const char *name) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + int res; + vfs_littlefs_dir_t *dir = NULL; + + dir = calloc(1, sizeof(vfs_littlefs_dir_t)); + if (dir == NULL) { + ESP_LOGE(TAG, "dir struct could not be malloced"); + goto exit; + } + + dir->path = strdup(name); + if (dir->path == NULL) { + ESP_LOGE(TAG, "dir path name could not be malloced"); + goto exit; + } + + sem_take(efs); + res = lfs_dir_open(efs->fs, &dir->d, dir->path); + sem_give(efs); + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to opendir \"%s\". Error %s (%d)", + dir->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to opendir \"%s\". Error %d", dir->path, res); +#endif + goto exit; + } + + return (DIR *)dir; + +exit: + esp_littlefs_dir_free(dir); + return NULL; +} + +static int vfs_littlefs_closedir(void *ctx, DIR *pdir) { + assert(pdir); + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + vfs_littlefs_dir_t *dir = (vfs_littlefs_dir_t *)pdir; + int res; + + sem_take(efs); + res = lfs_dir_close(efs->fs, &dir->d); + sem_give(efs); + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to closedir \"%s\". Error %s (%d)", + dir->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to closedir \"%s\". Error %d", dir->path, res); +#endif + return res; + } + + esp_littlefs_dir_free(dir); + return 0; +} + +static struct dirent *vfs_littlefs_readdir(void *ctx, DIR *pdir) { + assert(pdir); + vfs_littlefs_dir_t *dir = (vfs_littlefs_dir_t *)pdir; + int res; + struct dirent *out_dirent; + + res = vfs_littlefs_readdir_r(ctx, pdir, &dir->e, &out_dirent); + if (res != 0) return NULL; + return out_dirent; +} + +static int vfs_littlefs_readdir_r(void *ctx, DIR *pdir, + struct dirent *entry, struct dirent **out_dirent) { + assert(pdir); + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + vfs_littlefs_dir_t *dir = (vfs_littlefs_dir_t *)pdir; + int res; + struct lfs_info info = {0}; + + sem_take(efs); + do { /* Read until we get a real object name */ + res = lfs_dir_read(efs->fs, &dir->d, &info); + } while (res > 0 && (strcmp(info.name, ".") == 0 || strcmp(info.name, "..") == 0)); + sem_give(efs); + if (res < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGE(TAG, "Failed to readdir \"%s\". Error %s (%d)", + dir->path, esp_littlefs_errno(res), res); +#else + ESP_LOGE(TAG, "Failed to readdir \"%s\". Error %d", dir->path, res); +#endif + return -1; + } + + if (info.type == LFS_TYPE_REG) { + ESP_LOGD(TAG, "readdir a file of size %d named \"%s\"", + info.size, info.name); + } else { + ESP_LOGD(TAG, "readdir a dir named \"%s\"", info.name); + } + + if (res == 0) { + /* End of Objs */ + ESP_LOGD(TAG, "Reached the end of the directory."); + *out_dirent = NULL; + } else { + entry->d_ino = 0; + entry->d_type = info.type == LFS_TYPE_REG ? DT_REG : DT_DIR; + strncpy(entry->d_name, info.name, sizeof(entry->d_name)); + *out_dirent = entry; + } + dir->offset++; + + return 0; +} + +static long vfs_littlefs_telldir(void *ctx, DIR *pdir) { + assert(pdir); + vfs_littlefs_dir_t *dir = (vfs_littlefs_dir_t *)pdir; + return dir->offset; +} + +static void vfs_littlefs_seekdir(void *ctx, DIR *pdir, long offset) { + assert(pdir); + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + vfs_littlefs_dir_t *dir = (vfs_littlefs_dir_t *)pdir; + int res; + + if (offset < dir->offset) { + /* close and re-open dir to rewind to beginning */ + sem_take(efs); + res = lfs_dir_rewind(efs->fs, &dir->d); + sem_give(efs); + if (res < 0) { + ESP_LOGE(TAG, "Failed to rewind dir \"%s\". Error %s (%d)", + dir->path, esp_littlefs_errno(res), res); + return; + } + dir->offset = 0; + } + + while (dir->offset < offset) { + struct dirent *out_dirent; + res = vfs_littlefs_readdir_r(ctx, pdir, &dir->e, &out_dirent); + if (res != 0) { + ESP_LOGE(TAG, "Error readdir_r"); + return; + } + } +} + +static int vfs_littlefs_mkdir(void *ctx, const char *name, mode_t mode) { + /* Note: mode is currently unused */ + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + int res; + ESP_LOGD(TAG, "mkdir \"%s\"", name); + + sem_take(efs); + res = lfs_mkdir(efs->fs, name); + sem_give(efs); + if (res < 0) { + ESP_LOGE(TAG, "Failed to mkdir \"%s\". Error %s (%d)", + name, esp_littlefs_errno(res), res); + return res; + } + return 0; +} + +static int vfs_littlefs_rmdir(void *ctx, const char *name) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + struct lfs_info info; + int res; + + /* Error Checking */ + sem_take(efs); + res = lfs_stat(efs->fs, name, &info); + if (res < 0) { + sem_give(efs); + ESP_LOGE(TAG, "\"%s\" doesn't exist.", name); + return -1; + } + + if (info.type != LFS_TYPE_DIR) { + sem_give(efs); + ESP_LOGE(TAG, "\"%s\" is not a directory.", name); + return -1; + } + + /* Unlink the dir */ + res = lfs_remove(efs->fs, name); + sem_give(efs); + if (res < 0) { + ESP_LOGE(TAG, "Failed to unlink path \"%s\". Error %s (%d)", + name, esp_littlefs_errno(res), res); + return -1; + } + + return 0; +} + +#if CONFIG_LITTLEFS_USE_MTIME +/** + * Sets the mtime attr to t. + */ +static int vfs_littlefs_update_mtime_value(esp_littlefs_t *efs, const char *path, time_t t) { + int res; + res = lfs_setattr(efs->fs, path, LITTLEFS_ATTR_MTIME, + &t, sizeof(t)); + if (res < 0) { + ESP_LOGE(TAG, "Failed to update mtime (%d)", res); + } + + return res; +} + +/** + * Sets the mtime attr to an appropriate value + */ +static void vfs_littlefs_update_mtime(esp_littlefs_t *efs, const char *path) { + vfs_littlefs_utime(efs, path, NULL); +} + +static int vfs_littlefs_utime(void *ctx, const char *path, const struct utimbuf *times) { + esp_littlefs_t *efs = (esp_littlefs_t *)ctx; + time_t t; + + assert(path); + + if (times) { + t = times->modtime; + } else { +#if CONFIG_LITTLEFS_MTIME_USE_SECONDS + // use current time + t = time(NULL); +#elif CONFIG_LITTLEFS_MTIME_USE_NONCE + assert(sizeof(time_t) == 4); + t = vfs_littlefs_get_mtime(efs, path); + if (0 == t) + t = esp_random(); + else + t += 1; + + if (0 == t) t = 1; +#else +#error "Invalid MTIME configuration" +#endif + } + + int ret = vfs_littlefs_update_mtime_value(efs, path, t); + return ret; +} + +static time_t vfs_littlefs_get_mtime(esp_littlefs_t *efs, const char *path) { + time_t t = 0; + int size; + size = lfs_getattr(efs->fs, path, LITTLEFS_ATTR_MTIME, + &t, sizeof(t)); + if (size < 0) { +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + ESP_LOGI(TAG, "Failed to get mtime attribute %s (%d)", + esp_littlefs_errno(size), size); +#else + ESP_LOGI(TAG, "Failed to get mtime attribute %d", size); +#endif + } + return t; +} +#endif //CONFIG_LITTLEFS_USE_MTIME diff --git a/lib/LITTLEFS/src/esp_littlefs.h b/lib/LITTLEFS/src/esp_littlefs.h new file mode 100644 index 00000000..5f5d3e37 --- /dev/null +++ b/lib/LITTLEFS/src/esp_littlefs.h @@ -0,0 +1,114 @@ +#ifndef ESP_LITTLEFS_H__ +#define ESP_LITTLEFS_H__ + +#include +#include +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "esp_err.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include "sdkconfig.h" + +#include "lfs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum { + LITTLEFS_ATTR_MTIME, /**< Last Modified - time (seconds) */ + LITTLEFS_ATTR_MAX +}; + +/** + *Configuration structure for esp_vfs_littlefs_register. + */ +typedef struct { + const char *base_path; /**< Mounting point. */ + const char *partition_label; /**< Label of partition to use. */ + uint8_t format_if_mount_failed:1; /**< Format the file system if it fails to mount. */ + uint8_t dont_mount:1; /**< Don't attempt to mount or format. Overrides format_if_mount_failed */ +} esp_vfs_littlefs_conf_t; + +/** + * Register and mount littlefs to VFS with given path prefix. + * + * @param conf Pointer to esp_vfs_littlefs_conf_t configuration structure + * + * @return + * - ESP_OK if success + * - ESP_ERR_NO_MEM if objects could not be allocated + * - ESP_ERR_INVALID_STATE if already mounted or partition is encrypted + * - ESP_ERR_NOT_FOUND if partition for littlefs was not found + * - ESP_FAIL if mount or format fails + */ +esp_err_t esp_vfs_littlefs_register(const esp_vfs_littlefs_conf_t * conf); + +/** + * Unregister and unmount littlefs from VFS + * + * @param partition_label Label of the partition to unregister. + * + * @return + * - ESP_OK if successful + * - ESP_ERR_INVALID_STATE already unregistered + */ +esp_err_t esp_vfs_littlefs_unregister(const char* partition_label); + +/** + * Check if littlefs is mounted + * + * @param partition_label Label of the partition to check. + * + * @return + * - true if mounted + * - false if not mounted + */ +bool esp_littlefs_mounted(const char* partition_label); + +/** + * Format the littlefs partition + * + * @param partition_label Label of the partition to format. + * @return + * - ESP_OK if successful + * - ESP_FAIL on error + */ +esp_err_t esp_littlefs_format(const char* partition_label); + +/** + * Get information for littlefs + * + * @param partition_label Optional, label of the partition to get info for. + * @param[out] total_bytes Size of the file system + * @param[out] used_bytes Current used bytes in the file system + * + * @return + * - ESP_OK if success + * - ESP_ERR_INVALID_STATE if not mounted + */ +esp_err_t esp_littlefs_info(const char* partition_label, size_t *total_bytes, size_t *used_bytes); + +#if CONFIG_LITTLEFS_HUMAN_READABLE +/** + * @brief converts an enumerated lfs error into a string. + * @param lfs_errno The enumerated littlefs error. + */ +const char * esp_littlefs_errno(enum lfs_error lfs_errno); +#endif + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/lib/LITTLEFS/src/lfs.c b/lib/LITTLEFS/src/lfs.c new file mode 100644 index 00000000..eb832fa0 --- /dev/null +++ b/lib/LITTLEFS/src/lfs.c @@ -0,0 +1,4913 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs.h" +#include "lfs_util.h" + +#define LFS_BLOCK_NULL ((lfs_block_t)-1) +#define LFS_BLOCK_INLINE ((lfs_block_t)-2) + +/// Caching block device operations /// +static inline void lfs_cache_drop(lfs_t *lfs, lfs_cache_t *rcache) { + // do not zero, cheaper if cache is readonly or only going to be + // written with identical data (during relocates) + (void)lfs; + rcache->block = LFS_BLOCK_NULL; +} + +static inline void lfs_cache_zero(lfs_t *lfs, lfs_cache_t *pcache) { + // zero to avoid information leak + memset(pcache->buffer, 0xff, lfs->cfg->cache_size); + pcache->block = LFS_BLOCK_NULL; +} + +static int lfs_bd_read(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (block >= lfs->cfg->block_count || + off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && block == pcache->block && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (block == rcache->block && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + if (size >= hint && off % lfs->cfg->read_size == 0 && + size >= lfs->cfg->read_size) { + // bypass cache? + diff = lfs_aligndown(diff, lfs->cfg->read_size); + int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); + if (err) { + return err; + } + + data += diff; + off += diff; + size -= diff; + continue; + } + + // load to cache, first condition can no longer fail + LFS_ASSERT(block < lfs->cfg->block_count); + rcache->block = block; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min( + lfs_min( + lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->block_size) + - rcache->off, + lfs->cfg->cache_size); + int err = lfs->cfg->read(lfs->cfg, rcache->block, + rcache->off, rcache->buffer, rcache->size); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + } + + return 0; +} + +enum { + LFS_CMP_EQ = 0, + LFS_CMP_LT = 1, + LFS_CMP_GT = 2, +}; + +static int lfs_bd_cmp(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + + for (lfs_off_t i = 0; i < size; i++) { + uint8_t dat; + int err = lfs_bd_read(lfs, + pcache, rcache, hint-i, + block, off+i, &dat, 1); + if (err) { + return err; + } + + if (dat != data[i]) { + return (dat < data[i]) ? LFS_CMP_LT : LFS_CMP_GT; + } + } + + return LFS_CMP_EQ; +} + +static int lfs_bd_flush(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + if (pcache->block != LFS_BLOCK_NULL && pcache->block != LFS_BLOCK_INLINE) { + LFS_ASSERT(pcache->block < lfs->cfg->block_count); + lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size); + int err = lfs->cfg->prog(lfs->cfg, pcache->block, + pcache->off, pcache->buffer, diff); + LFS_ASSERT(err <= 0); + if (err) { + return err; + } + + if (validate) { + // check data on disk + lfs_cache_drop(lfs, rcache); + int res = lfs_bd_cmp(lfs, + NULL, rcache, diff, + pcache->block, pcache->off, pcache->buffer, diff); + if (res < 0) { + return res; + } + + if (res != LFS_CMP_EQ) { + return LFS_ERR_CORRUPT; + } + } + + lfs_cache_zero(lfs, pcache); + } + + return 0; +} + +static int lfs_bd_sync(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) { + lfs_cache_drop(lfs, rcache); + + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + + err = lfs->cfg->sync(lfs->cfg); + LFS_ASSERT(err <= 0); + return err; +} + +static int lfs_bd_prog(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate, + lfs_block_t block, lfs_off_t off, + const void *buffer, lfs_size_t size) { + const uint8_t *data = buffer; + LFS_ASSERT(block == LFS_BLOCK_INLINE || block < lfs->cfg->block_count); + LFS_ASSERT(off + size <= lfs->cfg->block_size); + + while (size > 0) { + if (block == pcache->block && + off >= pcache->off && + off < pcache->off + lfs->cfg->cache_size) { + // already fits in pcache? + lfs_size_t diff = lfs_min(size, + lfs->cfg->cache_size - (off-pcache->off)); + memcpy(&pcache->buffer[off-pcache->off], data, diff); + + data += diff; + off += diff; + size -= diff; + + pcache->size = lfs_max(pcache->size, off - pcache->off); + if (pcache->size == lfs->cfg->cache_size) { + // eagerly flush out pcache if we fill up + int err = lfs_bd_flush(lfs, pcache, rcache, validate); + if (err) { + return err; + } + } + + continue; + } + + // pcache must have been flushed, either by programming and + // entire block or manually flushing the pcache + LFS_ASSERT(pcache->block == LFS_BLOCK_NULL); + + // prepare pcache, first condition can no longer fail + pcache->block = block; + pcache->off = lfs_aligndown(off, lfs->cfg->prog_size); + pcache->size = 0; + } + + return 0; +} + +static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) { + LFS_ASSERT(block < lfs->cfg->block_count); + int err = lfs->cfg->erase(lfs->cfg, block); + LFS_ASSERT(err <= 0); + return err; +} + + +/// Small type-level utilities /// +// operations on block pairs +static inline void lfs_pair_swap(lfs_block_t pair[2]) { + lfs_block_t t = pair[0]; + pair[0] = pair[1]; + pair[1] = t; +} + +static inline bool lfs_pair_isnull(const lfs_block_t pair[2]) { + return pair[0] == LFS_BLOCK_NULL || pair[1] == LFS_BLOCK_NULL; +} + +static inline int lfs_pair_cmp( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return !(paira[0] == pairb[0] || paira[1] == pairb[1] || + paira[0] == pairb[1] || paira[1] == pairb[0]); +} + +static inline bool lfs_pair_sync( + const lfs_block_t paira[2], + const lfs_block_t pairb[2]) { + return (paira[0] == pairb[0] && paira[1] == pairb[1]) || + (paira[0] == pairb[1] && paira[1] == pairb[0]); +} + +static inline void lfs_pair_fromle32(lfs_block_t pair[2]) { + pair[0] = lfs_fromle32(pair[0]); + pair[1] = lfs_fromle32(pair[1]); +} + +static inline void lfs_pair_tole32(lfs_block_t pair[2]) { + pair[0] = lfs_tole32(pair[0]); + pair[1] = lfs_tole32(pair[1]); +} + +// operations on 32-bit entry tags +typedef uint32_t lfs_tag_t; +typedef int32_t lfs_stag_t; + +#define LFS_MKTAG(type, id, size) \ + (((lfs_tag_t)(type) << 20) | ((lfs_tag_t)(id) << 10) | (lfs_tag_t)(size)) + +#define LFS_MKTAG_IF(cond, type, id, size) \ + ((cond) ? LFS_MKTAG(type, id, size) : LFS_MKTAG(LFS_FROM_NOOP, 0, 0)) + +#define LFS_MKTAG_IF_ELSE(cond, type1, id1, size1, type2, id2, size2) \ + ((cond) ? LFS_MKTAG(type1, id1, size1) : LFS_MKTAG(type2, id2, size2)) + +static inline bool lfs_tag_isvalid(lfs_tag_t tag) { + return !(tag & 0x80000000); +} + +static inline bool lfs_tag_isdelete(lfs_tag_t tag) { + return ((int32_t)(tag << 22) >> 22) == -1; +} + +static inline uint16_t lfs_tag_type1(lfs_tag_t tag) { + return (tag & 0x70000000) >> 20; +} + +static inline uint16_t lfs_tag_type3(lfs_tag_t tag) { + return (tag & 0x7ff00000) >> 20; +} + +static inline uint8_t lfs_tag_chunk(lfs_tag_t tag) { + return (tag & 0x0ff00000) >> 20; +} + +static inline int8_t lfs_tag_splice(lfs_tag_t tag) { + return (int8_t)lfs_tag_chunk(tag); +} + +static inline uint16_t lfs_tag_id(lfs_tag_t tag) { + return (tag & 0x000ffc00) >> 10; +} + +static inline lfs_size_t lfs_tag_size(lfs_tag_t tag) { + return tag & 0x000003ff; +} + +static inline lfs_size_t lfs_tag_dsize(lfs_tag_t tag) { + return sizeof(tag) + lfs_tag_size(tag + lfs_tag_isdelete(tag)); +} + +// operations on attributes in attribute lists +struct lfs_mattr { + lfs_tag_t tag; + const void *buffer; +}; + +struct lfs_diskoff { + lfs_block_t block; + lfs_off_t off; +}; + +#define LFS_MKATTRS(...) \ + (struct lfs_mattr[]){__VA_ARGS__}, \ + sizeof((struct lfs_mattr[]){__VA_ARGS__}) / sizeof(struct lfs_mattr) + +// operations on global state +static inline void lfs_gstate_xor(lfs_gstate_t *a, const lfs_gstate_t *b) { + for (int i = 0; i < 3; i++) { + ((uint32_t*)a)[i] ^= ((const uint32_t*)b)[i]; + } +} + +static inline bool lfs_gstate_iszero(const lfs_gstate_t *a) { + for (int i = 0; i < 3; i++) { + if (((uint32_t*)a)[i] != 0) { + return false; + } + } + return true; +} + +static inline bool lfs_gstate_hasorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline uint8_t lfs_gstate_getorphans(const lfs_gstate_t *a) { + return lfs_tag_size(a->tag); +} + +static inline bool lfs_gstate_hasmove(const lfs_gstate_t *a) { + return lfs_tag_type1(a->tag); +} + +static inline bool lfs_gstate_hasmovehere(const lfs_gstate_t *a, + const lfs_block_t *pair) { + return lfs_tag_type1(a->tag) && lfs_pair_cmp(a->pair, pair) == 0; +} + +static inline void lfs_gstate_fromle32(lfs_gstate_t *a) { + a->tag = lfs_fromle32(a->tag); + a->pair[0] = lfs_fromle32(a->pair[0]); + a->pair[1] = lfs_fromle32(a->pair[1]); +} + +static inline void lfs_gstate_tole32(lfs_gstate_t *a) { + a->tag = lfs_tole32(a->tag); + a->pair[0] = lfs_tole32(a->pair[0]); + a->pair[1] = lfs_tole32(a->pair[1]); +} + +// other endianness operations +static void lfs_ctz_fromle32(struct lfs_ctz *ctz) { + ctz->head = lfs_fromle32(ctz->head); + ctz->size = lfs_fromle32(ctz->size); +} + +static void lfs_ctz_tole32(struct lfs_ctz *ctz) { + ctz->head = lfs_tole32(ctz->head); + ctz->size = lfs_tole32(ctz->size); +} + +static inline void lfs_superblock_fromle32(lfs_superblock_t *superblock) { + superblock->version = lfs_fromle32(superblock->version); + superblock->block_size = lfs_fromle32(superblock->block_size); + superblock->block_count = lfs_fromle32(superblock->block_count); + superblock->name_max = lfs_fromle32(superblock->name_max); + superblock->file_max = lfs_fromle32(superblock->file_max); + superblock->attr_max = lfs_fromle32(superblock->attr_max); +} + +static inline void lfs_superblock_tole32(lfs_superblock_t *superblock) { + superblock->version = lfs_tole32(superblock->version); + superblock->block_size = lfs_tole32(superblock->block_size); + superblock->block_count = lfs_tole32(superblock->block_count); + superblock->name_max = lfs_tole32(superblock->name_max); + superblock->file_max = lfs_tole32(superblock->file_max); + superblock->attr_max = lfs_tole32(superblock->attr_max); +} + + +/// Internal operations predeclared here /// +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount); +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end); +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file); +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file); +static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans); +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]); +static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *pdir); +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], + lfs_mdir_t *parent); +static int lfs_fs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], lfs_block_t newpair[2]); +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans); +static int lfs_fs_forceconsistency(lfs_t *lfs); +static int lfs_deinit(lfs_t *lfs); +#ifdef LFS_MIGRATE +static int lfs1_traverse(lfs_t *lfs, + int (*cb)(void*, lfs_block_t), void *data); +#endif + +/// Block allocator /// +static int lfs_alloc_lookahead(void *p, lfs_block_t block) { + lfs_t *lfs = (lfs_t*)p; + lfs_block_t off = ((block - lfs->free.off) + + lfs->cfg->block_count) % lfs->cfg->block_count; + + if (off < lfs->free.size) { + lfs->free.buffer[off / 32] |= 1U << (off % 32); + } + + return 0; +} + +static void lfs_alloc_ack(lfs_t *lfs) { + lfs->free.ack = lfs->cfg->block_count; +} + +// Invalidate the lookahead buffer. This is done during mounting and +// failed traversals +static void lfs_alloc_reset(lfs_t *lfs) { + lfs->free.off = lfs->seed % lfs->cfg->block_size; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); +} + +static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { + while (true) { + while (lfs->free.i != lfs->free.size) { + lfs_block_t off = lfs->free.i; + lfs->free.i += 1; + lfs->free.ack -= 1; + + if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { + // found a free block + *block = (lfs->free.off + off) % lfs->cfg->block_count; + + // eagerly find next off so an alloc ack can + // discredit old lookahead blocks + while (lfs->free.i != lfs->free.size && + (lfs->free.buffer[lfs->free.i / 32] + & (1U << (lfs->free.i % 32)))) { + lfs->free.i += 1; + lfs->free.ack -= 1; + } + + return 0; + } + } + + // check if we have looked at all blocks since last ack + if (lfs->free.ack == 0) { + LFS_ERROR("No more free space %"PRIu32, + lfs->free.i + lfs->free.off); + return LFS_ERR_NOSPC; + } + + lfs->free.off = (lfs->free.off + lfs->free.size) + % lfs->cfg->block_count; + lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, lfs->free.ack); + lfs->free.i = 0; + + // find mask of free blocks from tree + memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); + int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true); + if (err) { + lfs_alloc_reset(lfs); + return err; + } + } +} + +/// Metadata pair and directory operations /// +static lfs_stag_t lfs_dir_getslice(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t goff, void *gbuffer, lfs_size_t gsize) { + lfs_off_t off = dir->off; + lfs_tag_t ntag = dir->etag; + lfs_stag_t gdiff = 0; + + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair) && + lfs_tag_id(gmask) != 0 && + lfs_tag_id(lfs->gdisk.tag) <= lfs_tag_id(gtag)) { + // synthetic moves + gdiff -= LFS_MKTAG(0, 1, 0); + } + + // iterate over dir block backwards (for faster lookups) + while (off >= sizeof(lfs_tag_t) + lfs_tag_dsize(ntag)) { + off -= lfs_tag_dsize(ntag); + lfs_tag_t tag = ntag; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(ntag), + dir->pair[0], off, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + ntag = (lfs_frombe32(ntag) ^ tag) & 0x7fffffff; + + if (lfs_tag_id(gmask) != 0 && + lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(gtag - gdiff)) { + if (tag == (LFS_MKTAG(LFS_TYPE_CREATE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & (gtag - gdiff)))) { + // found where we were created + return LFS_ERR_NOENT; + } + + // move around splices + gdiff += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + if ((gmask & tag) == (gmask & (gtag - gdiff))) { + if (lfs_tag_isdelete(tag)) { + return LFS_ERR_NOENT; + } + + lfs_size_t diff = lfs_min(lfs_tag_size(tag), gsize); + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, diff, + dir->pair[0], off+sizeof(tag)+goff, gbuffer, diff); + if (err) { + return err; + } + + memset((uint8_t*)gbuffer + diff, 0, gsize - diff); + + return tag + gdiff; + } + } + + return LFS_ERR_NOENT; +} + +static lfs_stag_t lfs_dir_get(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_tag_t gmask, lfs_tag_t gtag, void *buffer) { + return lfs_dir_getslice(lfs, dir, + gmask, gtag, + 0, buffer, lfs_tag_size(gtag)); +} + +static int lfs_dir_getread(lfs_t *lfs, const lfs_mdir_t *dir, + const lfs_cache_t *pcache, lfs_cache_t *rcache, lfs_size_t hint, + lfs_tag_t gmask, lfs_tag_t gtag, + lfs_off_t off, void *buffer, lfs_size_t size) { + uint8_t *data = buffer; + if (off+size > lfs->cfg->block_size) { + return LFS_ERR_CORRUPT; + } + + while (size > 0) { + lfs_size_t diff = size; + + if (pcache && pcache->block == LFS_BLOCK_INLINE && + off < pcache->off + pcache->size) { + if (off >= pcache->off) { + // is already in pcache? + diff = lfs_min(diff, pcache->size - (off-pcache->off)); + memcpy(data, &pcache->buffer[off-pcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // pcache takes priority + diff = lfs_min(diff, pcache->off-off); + } + + if (rcache->block == LFS_BLOCK_INLINE && + off < rcache->off + rcache->size) { + if (off >= rcache->off) { + // is already in rcache? + diff = lfs_min(diff, rcache->size - (off-rcache->off)); + memcpy(data, &rcache->buffer[off-rcache->off], diff); + + data += diff; + off += diff; + size -= diff; + continue; + } + + // rcache takes priority + diff = lfs_min(diff, rcache->off-off); + } + + // load to cache, first condition can no longer fail + rcache->block = LFS_BLOCK_INLINE; + rcache->off = lfs_aligndown(off, lfs->cfg->read_size); + rcache->size = lfs_min(lfs_alignup(off+hint, lfs->cfg->read_size), + lfs->cfg->cache_size); + int err = lfs_dir_getslice(lfs, dir, gmask, gtag, + rcache->off, rcache->buffer, rcache->size); + if (err < 0) { + return err; + } + } + + return 0; +} + +static int lfs_dir_traverse_filter(void *p, + lfs_tag_t tag, const void *buffer) { + lfs_tag_t *filtertag = p; + (void)buffer; + + // which mask depends on unique bit in tag structure + uint32_t mask = (tag & LFS_MKTAG(0x100, 0, 0)) + ? LFS_MKTAG(0x7ff, 0x3ff, 0) + : LFS_MKTAG(0x700, 0x3ff, 0); + + // check for redundancy + if ((mask & tag) == (mask & *filtertag) || + lfs_tag_isdelete(*filtertag) || + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == ( + LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & *filtertag))) { + return true; + } + + // check if we need to adjust for created/deleted tags + if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE && + lfs_tag_id(tag) <= lfs_tag_id(*filtertag)) { + *filtertag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + + return false; +} + +static int lfs_dir_traverse(lfs_t *lfs, + const lfs_mdir_t *dir, lfs_off_t off, lfs_tag_t ptag, + const struct lfs_mattr *attrs, int attrcount, + lfs_tag_t tmask, lfs_tag_t ttag, + uint16_t begin, uint16_t end, int16_t diff, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // iterate over directory and attrs + while (true) { + lfs_tag_t tag; + const void *buffer; + struct lfs_diskoff disk; + if (off+lfs_tag_dsize(ptag) < dir->off) { + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + return err; + } + + tag = (lfs_frombe32(tag) ^ ptag) | 0x80000000; + disk.block = dir->pair[0]; + disk.off = off+sizeof(lfs_tag_t); + buffer = &disk; + ptag = tag; + } else if (attrcount > 0) { + tag = attrs[0].tag; + buffer = attrs[0].buffer; + attrs += 1; + attrcount -= 1; + } else { + return 0; + } + + lfs_tag_t mask = LFS_MKTAG(0x7ff, 0, 0); + if ((mask & tmask & tag) != (mask & tmask & ttag)) { + continue; + } + + // do we need to filter? inlining the filtering logic here allows + // for some minor optimizations + if (lfs_tag_id(tmask) != 0) { + // scan for duplicates and update tag based on creates/deletes + int filter = lfs_dir_traverse(lfs, + dir, off, ptag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_traverse_filter, &tag); + if (filter < 0) { + return filter; + } + + if (filter) { + continue; + } + + // in filter range? + if (!(lfs_tag_id(tag) >= begin && lfs_tag_id(tag) < end)) { + continue; + } + } + + // handle special cases for mcu-side operations + if (lfs_tag_type3(tag) == LFS_FROM_NOOP) { + // do nothing + } else if (lfs_tag_type3(tag) == LFS_FROM_MOVE) { + uint16_t fromid = lfs_tag_size(tag); + uint16_t toid = lfs_tag_id(tag); + int err = lfs_dir_traverse(lfs, + buffer, 0, 0xffffffff, NULL, 0, + LFS_MKTAG(0x600, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, 0, 0), + fromid, fromid+1, toid-fromid+diff, + cb, data); + if (err) { + return err; + } + } else if (lfs_tag_type3(tag) == LFS_FROM_USERATTRS) { + for (unsigned i = 0; i < lfs_tag_size(tag); i++) { + const struct lfs_attr *a = buffer; + int err = cb(data, LFS_MKTAG(LFS_TYPE_USERATTR + a[i].type, + lfs_tag_id(tag) + diff, a[i].size), a[i].buffer); + if (err) { + return err; + } + } + } else { + int err = cb(data, tag + LFS_MKTAG(0, diff, 0), buffer); + if (err) { + return err; + } + } + } +} + +static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2], + lfs_tag_t fmask, lfs_tag_t ftag, uint16_t *id, + int (*cb)(void *data, lfs_tag_t tag, const void *buffer), void *data) { + // we can find tag very efficiently during a fetch, since we're already + // scanning the entire directory + lfs_stag_t besttag = -1; + + // if either block address is invalid we return LFS_ERR_CORRUPT here, + // otherwise later writes to the pair could fail + if (pair[0] >= lfs->cfg->block_count || pair[1] >= lfs->cfg->block_count) { + return LFS_ERR_CORRUPT; + } + + // find the block with the most recent revision + uint32_t revs[2] = {0, 0}; + int r = 0; + for (int i = 0; i < 2; i++) { + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(revs[i]), + pair[i], 0, &revs[i], sizeof(revs[i])); + revs[i] = lfs_fromle32(revs[i]); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + if (err != LFS_ERR_CORRUPT && + lfs_scmp(revs[i], revs[(i+1)%2]) > 0) { + r = i; + } + } + + dir->pair[0] = pair[(r+0)%2]; + dir->pair[1] = pair[(r+1)%2]; + dir->rev = revs[(r+0)%2]; + dir->off = 0; // nonzero = found some commits + + // now scan tags to fetch the actual dir and find possible match + for (int i = 0; i < 2; i++) { + lfs_off_t off = 0; + lfs_tag_t ptag = 0xffffffff; + + uint16_t tempcount = 0; + lfs_block_t temptail[2] = {LFS_BLOCK_NULL, LFS_BLOCK_NULL}; + bool tempsplit = false; + lfs_stag_t tempbesttag = besttag; + + dir->rev = lfs_tole32(dir->rev); + uint32_t crc = lfs_crc(0xffffffff, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + + while (true) { + // extract next tag + lfs_tag_t tag; + off += lfs_tag_dsize(ptag); + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off, &tag, sizeof(tag)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + // can't continue? + dir->erased = false; + break; + } + return err; + } + + crc = lfs_crc(crc, &tag, sizeof(tag)); + tag = lfs_frombe32(tag) ^ ptag; + + // next commit not yet programmed or we're not in valid range + if (!lfs_tag_isvalid(tag)) { + dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && + dir->off % lfs->cfg->prog_size == 0); + break; + } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { + dir->erased = false; + break; + } + + ptag = tag; + + if (lfs_tag_type1(tag) == LFS_TYPE_CRC) { + // check the crc attr + uint32_t dcrc; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &dcrc, sizeof(dcrc)); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return err; + } + dcrc = lfs_fromle32(dcrc); + + if (crc != dcrc) { + dir->erased = false; + break; + } + + // reset the next bit if we need to + ptag ^= (lfs_tag_t)(lfs_tag_chunk(tag) & 1U) << 31; + + // toss our crc into the filesystem seed for + // pseudorandom numbers + lfs->seed ^= crc; + + // update with what's found so far + besttag = tempbesttag; + dir->off = off + lfs_tag_dsize(tag); + dir->etag = ptag; + dir->count = tempcount; + dir->tail[0] = temptail[0]; + dir->tail[1] = temptail[1]; + dir->split = tempsplit; + + // reset crc + crc = 0xffffffff; + continue; + } + + // crc the entry first, hopefully leaving it in the cache + for (lfs_off_t j = sizeof(tag); j < lfs_tag_dsize(tag); j++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+j, &dat, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return err; + } + + crc = lfs_crc(crc, &dat, 1); + } + + // directory modification tags? + if (lfs_tag_type1(tag) == LFS_TYPE_NAME) { + // increase count of files if necessary + if (lfs_tag_id(tag) >= tempcount) { + tempcount = lfs_tag_id(tag) + 1; + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_SPLICE) { + tempcount += lfs_tag_splice(tag); + + if (tag == (LFS_MKTAG(LFS_TYPE_DELETE, 0, 0) | + (LFS_MKTAG(0, 0x3ff, 0) & tempbesttag))) { + tempbesttag |= 0x80000000; + } else if (tempbesttag != -1 && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + tempbesttag += LFS_MKTAG(0, lfs_tag_splice(tag), 0); + } + } else if (lfs_tag_type1(tag) == LFS_TYPE_TAIL) { + tempsplit = (lfs_tag_chunk(tag) & 1); + + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, lfs->cfg->block_size, + dir->pair[0], off+sizeof(tag), &temptail, 8); + if (err) { + if (err == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + } + lfs_pair_fromle32(temptail); + } + + // found a match for our fetcher? + if ((fmask & tag) == (fmask & ftag)) { + int res = cb(data, tag, &(struct lfs_diskoff){ + dir->pair[0], off+sizeof(tag)}); + if (res < 0) { + if (res == LFS_ERR_CORRUPT) { + dir->erased = false; + break; + } + return res; + } + + if (res == LFS_CMP_EQ) { + // found a match + tempbesttag = tag; + } else if ((LFS_MKTAG(0x7ff, 0x3ff, 0) & tag) == + (LFS_MKTAG(0x7ff, 0x3ff, 0) & tempbesttag)) { + // found an identical tag, but contents didn't match + // this must mean that our besttag has been overwritten + tempbesttag = -1; + } else if (res == LFS_CMP_GT && + lfs_tag_id(tag) <= lfs_tag_id(tempbesttag)) { + // found a greater match, keep track to keep things sorted + tempbesttag = tag | 0x80000000; + } + } + } + + // consider what we have good enough + if (dir->off > 0) { + // synthetic move + if (lfs_gstate_hasmovehere(&lfs->gdisk, dir->pair)) { + if (lfs_tag_id(lfs->gdisk.tag) == lfs_tag_id(besttag)) { + besttag |= 0x80000000; + } else if (besttag != -1 && + lfs_tag_id(lfs->gdisk.tag) < lfs_tag_id(besttag)) { + besttag -= LFS_MKTAG(0, 1, 0); + } + } + + // found tag? or found best id? + if (id) { + *id = lfs_min(lfs_tag_id(besttag), dir->count); + } + + if (lfs_tag_isvalid(besttag)) { + return besttag; + } else if (lfs_tag_id(besttag) < dir->count) { + return LFS_ERR_NOENT; + } else { + return 0; + } + } + + // failed, try the other block? + lfs_pair_swap(dir->pair); + dir->rev = revs[(r+1)%2]; + } + + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + dir->pair[0], dir->pair[1]); + return LFS_ERR_CORRUPT; +} + +static int lfs_dir_fetch(lfs_t *lfs, + lfs_mdir_t *dir, const lfs_block_t pair[2]) { + // note, mask=-1, tag=-1 can never match a tag since this + // pattern has the invalid bit set + return (int)lfs_dir_fetchmatch(lfs, dir, pair, + (lfs_tag_t)-1, (lfs_tag_t)-1, NULL, NULL, NULL); +} + +static int lfs_dir_getgstate(lfs_t *lfs, const lfs_mdir_t *dir, + lfs_gstate_t *gstate) { + lfs_gstate_t temp; + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x7ff, 0, 0), + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0, sizeof(temp)), &temp); + if (res < 0 && res != LFS_ERR_NOENT) { + return res; + } + + if (res != LFS_ERR_NOENT) { + // xor together to find resulting gstate + lfs_gstate_fromle32(&temp); + lfs_gstate_xor(gstate, &temp); + } + + return 0; +} + +static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir, + uint16_t id, struct lfs_info *info) { + if (id == 0x3ff) { + // special case for root + strcpy(info->name, "/"); + info->type = LFS_TYPE_DIR; + return 0; + } + + lfs_stag_t tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x780, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max+1), info->name); + if (tag < 0) { + return (int)tag; + } + + info->type = lfs_tag_type3(tag); + + struct lfs_ctz ctz; + tag = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + return (int)tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + info->size = ctz.size; + } else if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + info->size = lfs_tag_size(tag); + } + + return 0; +} + +struct lfs_dir_find_match { + lfs_t *lfs; + const void *name; + lfs_size_t size; +}; + +static int lfs_dir_find_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_dir_find_match *name = data; + lfs_t *lfs = name->lfs; + const struct lfs_diskoff *disk = buffer; + + // compare with disk + lfs_size_t diff = lfs_min(name->size, lfs_tag_size(tag)); + int res = lfs_bd_cmp(lfs, + NULL, &lfs->rcache, diff, + disk->block, disk->off, name->name, diff); + if (res != LFS_CMP_EQ) { + return res; + } + + // only equal if our size is still the same + if (name->size != lfs_tag_size(tag)) { + return (name->size < lfs_tag_size(tag)) ? LFS_CMP_LT : LFS_CMP_GT; + } + + // found a match! + return LFS_CMP_EQ; +} + +static lfs_stag_t lfs_dir_find(lfs_t *lfs, lfs_mdir_t *dir, + const char **path, uint16_t *id) { + // we reduce path to a single name if we can find it + const char *name = *path; + if (id) { + *id = 0x3ff; + } + + // default to root dir + lfs_stag_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0); + dir->tail[0] = lfs->root[0]; + dir->tail[1] = lfs->root[1]; + + while (true) { +nextname: + // skip slashes + name += strspn(name, "/"); + lfs_size_t namelen = strcspn(name, "/"); + + // skip '.' and root '..' + if ((namelen == 1 && memcmp(name, ".", 1) == 0) || + (namelen == 2 && memcmp(name, "..", 2) == 0)) { + name += namelen; + goto nextname; + } + + // skip if matched by '..' in name + const char *suffix = name + namelen; + lfs_size_t sufflen; + int depth = 1; + while (true) { + suffix += strspn(suffix, "/"); + sufflen = strcspn(suffix, "/"); + if (sufflen == 0) { + break; + } + + if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { + depth -= 1; + if (depth == 0) { + name = suffix + sufflen; + goto nextname; + } + } else { + depth += 1; + } + + suffix += sufflen; + } + + // found path + if (name[0] == '\0') { + return tag; + } + + // update what we've found so far + *path = name; + + // only continue if we hit a directory + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + return LFS_ERR_NOTDIR; + } + + // grab the entry data + if (lfs_tag_id(tag) != 0x3ff) { + lfs_stag_t res = lfs_dir_get(lfs, dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), dir->tail); + if (res < 0) { + return res; + } + lfs_pair_fromle32(dir->tail); + } + + // find entry matching name + while (true) { + tag = lfs_dir_fetchmatch(lfs, dir, dir->tail, + LFS_MKTAG(0x780, 0, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), + // are we last name? + (strchr(name, '/') == NULL) ? id : NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, name, namelen}); + if (tag < 0) { + return tag; + } + + if (tag) { + break; + } + + if (!dir->split) { + return LFS_ERR_NOENT; + } + } + + // to next name + name += namelen; + } +} + +// commit logic +struct lfs_commit { + lfs_block_t block; + lfs_off_t off; + lfs_tag_t ptag; + uint32_t crc; + + lfs_off_t begin; + lfs_off_t end; +}; + +static int lfs_dir_commitprog(lfs_t *lfs, struct lfs_commit *commit, + const void *buffer, lfs_size_t size) { + int err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off , + (const uint8_t*)buffer, size); + if (err) { + return err; + } + + commit->crc = lfs_crc(commit->crc, buffer, size); + commit->off += size; + return 0; +} + +static int lfs_dir_commitattr(lfs_t *lfs, struct lfs_commit *commit, + lfs_tag_t tag, const void *buffer) { + // check if we fit + lfs_size_t dsize = lfs_tag_dsize(tag); + if (commit->off + dsize > commit->end) { + return LFS_ERR_NOSPC; + } + + // write out tag + lfs_tag_t ntag = lfs_tobe32((tag & 0x7fffffff) ^ commit->ptag); + int err = lfs_dir_commitprog(lfs, commit, &ntag, sizeof(ntag)); + if (err) { + return err; + } + + if (!(tag & 0x80000000)) { + // from memory + err = lfs_dir_commitprog(lfs, commit, buffer, dsize-sizeof(tag)); + if (err) { + return err; + } + } else { + // from disk + const struct lfs_diskoff *disk = buffer; + for (lfs_off_t i = 0; i < dsize-sizeof(tag); i++) { + // rely on caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dsize-sizeof(tag)-i, + disk->block, disk->off+i, &dat, 1); + if (err) { + return err; + } + + err = lfs_dir_commitprog(lfs, commit, &dat, 1); + if (err) { + return err; + } + } + } + + commit->ptag = tag & 0x7fffffff; + return 0; +} + +static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { + const lfs_off_t off1 = commit->off; + const uint32_t crc1 = commit->crc; + // align to program units + const lfs_off_t end = lfs_alignup(off1 + 2*sizeof(uint32_t), + lfs->cfg->prog_size); + + // create crc tags to fill up remainder of commit, note that + // padding is not crced, which lets fetches skip padding but + // makes committing a bit more complicated + while (commit->off < end) { + lfs_off_t off = commit->off + sizeof(lfs_tag_t); + lfs_off_t noff = lfs_min(end - off, 0x3fe) + off; + if (noff < end) { + noff = lfs_min(noff, end - 2*sizeof(uint32_t)); + } + + // read erased state from next program unit + lfs_tag_t tag = 0xffffffff; + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(tag), + commit->block, noff, &tag, sizeof(tag)); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // build crc tag + bool reset = ~lfs_frombe32(tag) >> 31; + tag = LFS_MKTAG(LFS_TYPE_CRC + reset, 0x3ff, noff - off); + + // write out crc + uint32_t footer[2]; + footer[0] = lfs_tobe32(tag ^ commit->ptag); + commit->crc = lfs_crc(commit->crc, &footer[0], sizeof(footer[0])); + footer[1] = lfs_tole32(commit->crc); + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, false, + commit->block, commit->off, &footer, sizeof(footer)); + if (err) { + return err; + } + + commit->off += sizeof(tag)+lfs_tag_size(tag); + commit->ptag = tag ^ ((lfs_tag_t)reset << 31); + commit->crc = 0xffffffff; // reset crc for next "commit" + } + + // flush buffers + int err = lfs_bd_sync(lfs, &lfs->pcache, &lfs->rcache, false); + if (err) { + return err; + } + + // successful commit, check checksums to make sure + lfs_off_t off = commit->begin; + lfs_off_t noff = off1 + sizeof(uint32_t); + while (off < end) { + uint32_t crc = 0xffffffff; + for (lfs_off_t i = off; i < noff+sizeof(uint32_t); i++) { + // check against written crc, may catch blocks that + // become readonly and match our commit size exactly + if (i == off1 && crc != crc1) { + return LFS_ERR_CORRUPT; + } + + // leave it up to caching to make this efficient + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, noff+sizeof(uint32_t)-i, + commit->block, i, &dat, 1); + if (err) { + return err; + } + + crc = lfs_crc(crc, &dat, 1); + } + + // detected write error? + if (crc != 0) { + return LFS_ERR_CORRUPT; + } + + // skip padding + off = lfs_min(end - noff, 0x3fe) + noff; + if (off < end) { + off = lfs_min(off, end - 2*sizeof(uint32_t)); + } + noff = off + sizeof(uint32_t); + } + + return 0; +} + +static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { + // allocate pair of dir blocks (backwards, so we write block 1 first) + for (int i = 0; i < 2; i++) { + int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); + if (err) { + return err; + } + } + + // zero for reproducability in case initial block is unreadable + dir->rev = 0; + + // rather than clobbering one of the blocks we just pretend + // the revision may be valid + int err = lfs_bd_read(lfs, + NULL, &lfs->rcache, sizeof(dir->rev), + dir->pair[0], 0, &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err && err != LFS_ERR_CORRUPT) { + return err; + } + + // make sure we don't immediately evict + dir->rev += dir->rev & 1; + + // set defaults + dir->off = sizeof(dir->rev); + dir->etag = 0xffffffff; + dir->count = 0; + dir->tail[0] = LFS_BLOCK_NULL; + dir->tail[1] = LFS_BLOCK_NULL; + dir->erased = false; + dir->split = false; + + // don't write out yet, let caller take care of that + return 0; +} + +static int lfs_dir_drop(lfs_t *lfs, lfs_mdir_t *dir, lfs_mdir_t *tail) { + // steal state + int err = lfs_dir_getgstate(lfs, tail, &lfs->gdelta); + if (err) { + return err; + } + + // steal tail + lfs_pair_tole32(tail->tail); + err = lfs_dir_commit(lfs, dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_TAIL + tail->split, 0x3ff, 8), tail->tail})); + lfs_pair_fromle32(tail->tail); + if (err) { + return err; + } + + return 0; +} + +static int lfs_dir_split(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t split, uint16_t end) { + // create tail directory + lfs_alloc_ack(lfs); + lfs_mdir_t tail; + int err = lfs_dir_alloc(lfs, &tail); + if (err) { + return err; + } + + tail.split = dir->split; + tail.tail[0] = dir->tail[0]; + tail.tail[1] = dir->tail[1]; + + err = lfs_dir_compact(lfs, &tail, attrs, attrcount, source, split, end); + if (err) { + return err; + } + + dir->tail[0] = tail.pair[0]; + dir->tail[1] = tail.pair[1]; + dir->split = true; + + // update root if needed + if (lfs_pair_cmp(dir->pair, lfs->root) == 0 && split == 0) { + lfs->root[0] = tail.pair[0]; + lfs->root[1] = tail.pair[1]; + } + + return 0; +} + +static int lfs_dir_commit_size(void *p, lfs_tag_t tag, const void *buffer) { + lfs_size_t *size = p; + (void)buffer; + + *size += lfs_tag_dsize(tag); + return 0; +} + +struct lfs_dir_commit_commit { + lfs_t *lfs; + struct lfs_commit *commit; +}; + +static int lfs_dir_commit_commit(void *p, lfs_tag_t tag, const void *buffer) { + struct lfs_dir_commit_commit *commit = p; + return lfs_dir_commitattr(commit->lfs, commit->commit, tag, buffer); +} + +static int lfs_dir_compact(lfs_t *lfs, + lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, + lfs_mdir_t *source, uint16_t begin, uint16_t end) { + // save some state in case block is bad + const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; + bool relocated = false; + bool tired = false; + + // should we split? + while (end - begin > 1) { + // find size + lfs_size_t size = 0; + int err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_size, &size); + if (err) { + return err; + } + + // space is complicated, we need room for tail, crc, gstate, + // cleanup delete, and we cap at half a block to give room + // for metadata updates. + if (end - begin < 0xff && + size <= lfs_min(lfs->cfg->block_size - 36, + lfs_alignup(lfs->cfg->block_size/2, + lfs->cfg->prog_size))) { + break; + } + + // can't fit, need to split, we should really be finding the + // largest size that fits with a small binary search, but right now + // it's not worth the code size + uint16_t split = (end - begin) / 2; + err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin+split, end); + if (err) { + // if we fail to split, we may be able to overcompact, unless + // we're too big for even the full block, in which case our + // only option is to error + if (err == LFS_ERR_NOSPC && size <= lfs->cfg->block_size - 36) { + break; + } + return err; + } + + end = begin + split; + } + + // increment revision count + dir->rev += 1; + // If our revision count == n * block_cycles, we should force a relocation, + // this is how littlefs wear-levels at the metadata-pair level. Note that we + // actually use (block_cycles+1)|1, this is to avoid two corner cases: + // 1. block_cycles = 1, which would prevent relocations from terminating + // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate + // one metadata block in the pair, effectively making this useless + if (lfs->cfg->block_cycles > 0 && + (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) { + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + // oh no! we're writing too much to the superblock, + // should we expand? + lfs_ssize_t res = lfs_fs_size(lfs); + if (res < 0) { + return res; + } + + // do we have extra space? littlefs can't reclaim this space + // by itself, so expand cautiously + if ((lfs_size_t)res < lfs->cfg->block_count/2) { + LFS_DEBUG("Expanding superblock at rev %"PRIu32, dir->rev); + int err = lfs_dir_split(lfs, dir, attrs, attrcount, + source, begin, end); + if (err && err != LFS_ERR_NOSPC) { + return err; + } + + // welp, we tried, if we ran out of space there's not much + // we can do, we'll error later if we've become frozen + if (!err) { + end = begin; + } + } +#ifdef LFS_MIGRATE + } else if (lfs->lfs1) { + // do not proactively relocate blocks during migrations, this + // can cause a number of failure states such: clobbering the + // v1 superblock if we relocate root, and invalidating directory + // pointers if we relocate the head of a directory. On top of + // this, relocations increase the overall complexity of + // lfs_migration, which is already a delicate operation. +#endif + } else { + // we're writing too much, time to relocate + tired = true; + goto relocate; + } + } + + // begin loop to commit compaction to blocks until a compact sticks + while (true) { + { + // setup commit state + struct lfs_commit commit = { + .block = dir->pair[1], + .off = 0, + .ptag = 0xffffffff, + .crc = 0xffffffff, + + .begin = 0, + .end = lfs->cfg->block_size - 8, + }; + + // erase block to write to + int err = lfs_bd_erase(lfs, dir->pair[1]); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // write out header + dir->rev = lfs_tole32(dir->rev); + err = lfs_dir_commitprog(lfs, &commit, + &dir->rev, sizeof(dir->rev)); + dir->rev = lfs_fromle32(dir->rev); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // traverse the directory, this time writing out all unique tags + err = lfs_dir_traverse(lfs, + source, 0, 0xffffffff, attrs, attrcount, + LFS_MKTAG(0x400, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_NAME, 0, 0), + begin, end, -begin, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // commit tail, which may be new after last size check + if (!lfs_pair_isnull(dir->tail)) { + lfs_pair_tole32(dir->tail); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_TAIL + dir->split, 0x3ff, 8), + dir->tail); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // bring over gstate? + lfs_gstate_t delta = {0}; + if (!relocated) { + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gstate); + } + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + return err; + } + + if (!lfs_gstate_iszero(&delta)) { + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // complete commit with crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // successful compaction, swap dir pair to indicate most recent + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + lfs_pair_swap(dir->pair); + dir->count = end - begin; + dir->off = commit.off; + dir->etag = commit.ptag; + // update gstate + lfs->gdelta = (lfs_gstate_t){0}; + if (!relocated) { + lfs->gdisk = lfs->gstate; + } + } + break; + +relocate: + // commit was corrupted, drop caches and prepare to relocate block + relocated = true; + lfs_cache_drop(lfs, &lfs->pcache); + if (!tired) { + LFS_DEBUG("Bad block at 0x%"PRIx32, dir->pair[1]); + } + + // can't relocate superblock, filesystem is now frozen + if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { + LFS_WARN("Superblock 0x%"PRIx32" has become unwritable", + dir->pair[1]); + return LFS_ERR_NOSPC; + } + + // relocate half of pair + int err = lfs_alloc(lfs, &dir->pair[1]); + if (err && (err != LFS_ERR_NOSPC || !tired)) { + return err; + } + + tired = false; + continue; + } + + if (relocated) { + // update references if we relocated + LFS_DEBUG("Relocating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); + int err = lfs_fs_relocate(lfs, oldpair, dir->pair); + if (err) { + return err; + } + } + + return 0; +} + +static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, + const struct lfs_mattr *attrs, int attrcount) { + // check for any inline files that aren't RAM backed and + // forcefully evict them, needed for filesystem consistency + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (dir != &f->m && lfs_pair_cmp(f->m.pair, dir->pair) == 0 && + f->type == LFS_TYPE_REG && (f->flags & LFS_F_INLINE) && + f->ctz.size > lfs->cfg->cache_size) { + int err = lfs_file_outline(lfs, f); + if (err) { + return err; + } + + err = lfs_file_flush(lfs, f); + if (err) { + return err; + } + } + } + + // calculate changes to the directory + lfs_mdir_t olddir = *dir; + bool hasdelete = false; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { + dir->count += 1; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE) { + LFS_ASSERT(dir->count > 0); + dir->count -= 1; + hasdelete = true; + } else if (lfs_tag_type1(attrs[i].tag) == LFS_TYPE_TAIL) { + dir->tail[0] = ((lfs_block_t*)attrs[i].buffer)[0]; + dir->tail[1] = ((lfs_block_t*)attrs[i].buffer)[1]; + dir->split = (lfs_tag_chunk(attrs[i].tag) & 1); + lfs_pair_fromle32(dir->tail); + } + } + + // should we actually drop the directory block? + if (hasdelete && dir->count == 0) { + lfs_mdir_t pdir; + int err = lfs_fs_pred(lfs, dir->pair, &pdir); + if (err && err != LFS_ERR_NOENT) { + *dir = olddir; + return err; + } + + if (err != LFS_ERR_NOENT && pdir.split) { + err = lfs_dir_drop(lfs, &pdir, dir); + if (err) { + *dir = olddir; + return err; + } + } + } + + if (dir->erased || dir->count >= 0xff) { + // try to commit + struct lfs_commit commit = { + .block = dir->pair[0], + .off = dir->off, + .ptag = dir->etag, + .crc = 0xffffffff, + + .begin = dir->off, + .end = lfs->cfg->block_size - 8, + }; + + // traverse attrs that need to be written out + lfs_pair_tole32(dir->tail); + int err = lfs_dir_traverse(lfs, + dir, dir->off, dir->etag, attrs, attrcount, + 0, 0, 0, 0, 0, + lfs_dir_commit_commit, &(struct lfs_dir_commit_commit){ + lfs, &commit}); + lfs_pair_fromle32(dir->tail); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + + // commit any global diffs if we have any + lfs_gstate_t delta = {0}; + lfs_gstate_xor(&delta, &lfs->gstate); + lfs_gstate_xor(&delta, &lfs->gdisk); + lfs_gstate_xor(&delta, &lfs->gdelta); + delta.tag &= ~LFS_MKTAG(0, 0, 0x3ff); + if (!lfs_gstate_iszero(&delta)) { + err = lfs_dir_getgstate(lfs, dir, &delta); + if (err) { + *dir = olddir; + return err; + } + + lfs_gstate_tole32(&delta); + err = lfs_dir_commitattr(lfs, &commit, + LFS_MKTAG(LFS_TYPE_MOVESTATE, 0x3ff, + sizeof(delta)), &delta); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + } + + // finalize commit with the crc + err = lfs_dir_commitcrc(lfs, &commit); + if (err) { + if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { + goto compact; + } + *dir = olddir; + return err; + } + + // successful commit, update dir + LFS_ASSERT(commit.off % lfs->cfg->prog_size == 0); + dir->off = commit.off; + dir->etag = commit.ptag; + // and update gstate + lfs->gdisk = lfs->gstate; + lfs->gdelta = (lfs_gstate_t){0}; + } else { +compact: + // fall back to compaction + lfs_cache_drop(lfs, &lfs->pcache); + + int err = lfs_dir_compact(lfs, dir, attrs, attrcount, + dir, 0, dir->count); + if (err) { + *dir = olddir; + return err; + } + } + + // this complicated bit of logic is for fixing up any active + // metadata-pairs that we may have affected + // + // note we have to make two passes since the mdir passed to + // lfs_dir_commit could also be in this list, and even then + // we need to copy the pair so they don't get clobbered if we refetch + // our mdir. + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (&d->m != dir && lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { + d->m = *dir; + for (int i = 0; i < attrcount; i++) { + if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id == lfs_tag_id(attrs[i].tag)) { + d->m.pair[0] = LFS_BLOCK_NULL; + d->m.pair[1] = LFS_BLOCK_NULL; + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && + d->id > lfs_tag_id(attrs[i].tag)) { + d->id -= 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos -= 1; + } + } else if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE && + d->id >= lfs_tag_id(attrs[i].tag)) { + d->id += 1; + if (d->type == LFS_TYPE_DIR) { + ((lfs_dir_t*)d)->pos += 1; + } + } + } + } + } + + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { + while (d->id >= d->m.count && d->m.split) { + // we split and id is on tail now + d->id -= d->m.count; + int err = lfs_dir_fetch(lfs, &d->m, d->m.tail); + if (err) { + return err; + } + } + } + } + + return 0; +} + + +/// Top level directory operations /// +int lfs_mkdir(lfs_t *lfs, const char *path) { + LFS_TRACE("lfs_mkdir(%p, \"%s\")", (void*)lfs, path); + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + struct lfs_mlist cwd; + cwd.next = lfs->mlist; + uint16_t id; + err = lfs_dir_find(lfs, &cwd.m, &path, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + LFS_TRACE("lfs_mkdir -> %d", (err < 0) ? err : LFS_ERR_EXIST); + return (err < 0) ? err : LFS_ERR_EXIST; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + LFS_TRACE("lfs_mkdir -> %d", LFS_ERR_NAMETOOLONG); + return LFS_ERR_NAMETOOLONG; + } + + // build up new directory + lfs_alloc_ack(lfs); + lfs_mdir_t dir; + err = lfs_dir_alloc(lfs, &dir); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + // find end of list + lfs_mdir_t pred = cwd.m; + while (pred.split) { + err = lfs_dir_fetch(lfs, &pred, pred.tail); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + } + + // setup dir + lfs_pair_tole32(pred.tail); + err = lfs_dir_commit(lfs, &dir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pred.tail})); + lfs_pair_fromle32(pred.tail); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + // current block end of list? + if (cwd.m.split) { + // update tails, this creates a desync + lfs_fs_preporphans(lfs, +1); + + // it's possible our predecessor has to be relocated, and if + // our parent is our predecessor's predecessor, this could have + // caused our parent to go out of date, fortunately we can hook + // ourselves into littlefs to catch this + cwd.type = 0; + cwd.id = 0; + lfs->mlist = &cwd; + + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &pred, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + lfs->mlist = cwd.next; + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + lfs->mlist = cwd.next; + lfs_fs_preporphans(lfs, -1); + } + + // now insert into our parent block + lfs_pair_tole32(dir.pair); + err = lfs_dir_commit(lfs, &cwd.m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_DIR, id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_DIRSTRUCT, id, 8), dir.pair}, + {LFS_MKTAG_IF(!cwd.m.split, + LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir.pair})); + lfs_pair_fromle32(dir.pair); + if (err) { + LFS_TRACE("lfs_mkdir -> %d", err); + return err; + } + + LFS_TRACE("lfs_mkdir -> %d", 0); + return 0; +} + +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) { + LFS_TRACE("lfs_dir_open(%p, %p, \"%s\")", (void*)lfs, (void*)dir, path); + lfs_stag_t tag = lfs_dir_find(lfs, &dir->m, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_dir_open -> %"PRId32, tag); + return tag; + } + + if (lfs_tag_type3(tag) != LFS_TYPE_DIR) { + LFS_TRACE("lfs_dir_open -> %d", LFS_ERR_NOTDIR); + return LFS_ERR_NOTDIR; + } + + lfs_block_t pair[2]; + if (lfs_tag_id(tag) == 0x3ff) { + // handle root dir separately + pair[0] = lfs->root[0]; + pair[1] = lfs->root[1]; + } else { + // get dir pair from parent + lfs_stag_t res = lfs_dir_get(lfs, &dir->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + LFS_TRACE("lfs_dir_open -> %"PRId32, res); + return res; + } + lfs_pair_fromle32(pair); + } + + // fetch first pair + int err = lfs_dir_fetch(lfs, &dir->m, pair); + if (err) { + LFS_TRACE("lfs_dir_open -> %d", err); + return err; + } + + // setup entry + dir->head[0] = dir->m.pair[0]; + dir->head[1] = dir->m.pair[1]; + dir->id = 0; + dir->pos = 0; + + // add to list of mdirs + dir->type = LFS_TYPE_DIR; + dir->next = (lfs_dir_t*)lfs->mlist; + lfs->mlist = (struct lfs_mlist*)dir; + + LFS_TRACE("lfs_dir_open -> %d", 0); + return 0; +} + +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_close(%p, %p)", (void*)lfs, (void*)dir); + // remove from list of mdirs + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)dir) { + *p = (*p)->next; + break; + } + } + + LFS_TRACE("lfs_dir_close -> %d", 0); + return 0; +} + +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) { + LFS_TRACE("lfs_dir_read(%p, %p, %p)", + (void*)lfs, (void*)dir, (void*)info); + memset(info, 0, sizeof(*info)); + + // special offset for '.' and '..' + if (dir->pos == 0) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, "."); + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; + } else if (dir->pos == 1) { + info->type = LFS_TYPE_DIR; + strcpy(info->name, ".."); + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; + } + + while (true) { + if (dir->id == dir->m.count) { + if (!dir->m.split) { + LFS_TRACE("lfs_dir_read -> %d", false); + return false; + } + + int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + LFS_TRACE("lfs_dir_read -> %d", err); + return err; + } + + dir->id = 0; + } + + int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info); + if (err && err != LFS_ERR_NOENT) { + LFS_TRACE("lfs_dir_read -> %d", err); + return err; + } + + dir->id += 1; + if (err != LFS_ERR_NOENT) { + break; + } + } + + dir->pos += 1; + LFS_TRACE("lfs_dir_read -> %d", true); + return true; +} + +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) { + LFS_TRACE("lfs_dir_seek(%p, %p, %"PRIu32")", + (void*)lfs, (void*)dir, off); + // simply walk from head dir + int err = lfs_dir_rewind(lfs, dir); + if (err) { + LFS_TRACE("lfs_dir_seek -> %d", err); + return err; + } + + // first two for ./.. + dir->pos = lfs_min(2, off); + off -= dir->pos; + + // skip superblock entry + dir->id = (off > 0 && lfs_pair_cmp(dir->head, lfs->root) == 0); + + while (off > 0) { + int diff = lfs_min(dir->m.count - dir->id, off); + dir->id += diff; + dir->pos += diff; + off -= diff; + + if (dir->id == dir->m.count) { + if (!dir->m.split) { + LFS_TRACE("lfs_dir_seek -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail); + if (err) { + LFS_TRACE("lfs_dir_seek -> %d", err); + return err; + } + + dir->id = 0; + } + } + + LFS_TRACE("lfs_dir_seek -> %d", 0); + return 0; +} + +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_tell(%p, %p)", (void*)lfs, (void*)dir); + (void)lfs; + LFS_TRACE("lfs_dir_tell -> %"PRId32, dir->pos); + return dir->pos; +} + +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) { + LFS_TRACE("lfs_dir_rewind(%p, %p)", (void*)lfs, (void*)dir); + // reload the head dir + int err = lfs_dir_fetch(lfs, &dir->m, dir->head); + if (err) { + LFS_TRACE("lfs_dir_rewind -> %d", err); + return err; + } + + dir->id = 0; + dir->pos = 0; + LFS_TRACE("lfs_dir_rewind -> %d", 0); + return 0; +} + + +/// File index list operations /// +static int lfs_ctz_index(lfs_t *lfs, lfs_off_t *off) { + lfs_off_t size = *off; + lfs_off_t b = lfs->cfg->block_size - 2*4; + lfs_off_t i = size / b; + if (i == 0) { + return 0; + } + + i = (size - 4*(lfs_popc(i-1)+2)) / b; + *off = size - b*i - 4*lfs_popc(i); + return i; +} + +static int lfs_ctz_find(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) { + if (size == 0) { + *block = LFS_BLOCK_NULL; + *off = 0; + return 0; + } + + lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + lfs_off_t target = lfs_ctz_index(lfs, &pos); + + while (current > target) { + lfs_size_t skip = lfs_min( + lfs_npw2(current-target+1) - 1, + lfs_ctz(current)); + + int err = lfs_bd_read(lfs, + pcache, rcache, sizeof(head), + head, 4*skip, &head, sizeof(head)); + head = lfs_fromle32(head); + if (err) { + return err; + } + + current -= 1 << skip; + } + + *block = head; + *off = pos; + return 0; +} + +static int lfs_ctz_extend(lfs_t *lfs, + lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + lfs_block_t *block, lfs_off_t *off) { + while (true) { + // go ahead and grab a block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + { + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (size == 0) { + *block = nblock; + *off = 0; + return 0; + } + + lfs_size_t noff = size - 1; + lfs_off_t index = lfs_ctz_index(lfs, &noff); + noff = noff + 1; + + // just copy out the last block if it is incomplete + if (noff != lfs->cfg->block_size) { + for (lfs_off_t i = 0; i < noff; i++) { + uint8_t data; + err = lfs_bd_read(lfs, + NULL, rcache, noff-i, + head, i, &data, 1); + if (err) { + return err; + } + + err = lfs_bd_prog(lfs, + pcache, rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + *block = nblock; + *off = noff; + return 0; + } + + // append block + index += 1; + lfs_size_t skips = lfs_ctz(index) + 1; + lfs_block_t nhead = head; + for (lfs_off_t i = 0; i < skips; i++) { + nhead = lfs_tole32(nhead); + err = lfs_bd_prog(lfs, pcache, rcache, true, + nblock, 4*i, &nhead, 4); + nhead = lfs_fromle32(nhead); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + if (i != skips-1) { + err = lfs_bd_read(lfs, + NULL, rcache, sizeof(nhead), + nhead, 4*i, &nhead, sizeof(nhead)); + nhead = lfs_fromle32(nhead); + if (err) { + return err; + } + } + } + + *block = nblock; + *off = 4*skips; + return 0; + } + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, pcache); + } +} + +static int lfs_ctz_traverse(lfs_t *lfs, + const lfs_cache_t *pcache, lfs_cache_t *rcache, + lfs_block_t head, lfs_size_t size, + int (*cb)(void*, lfs_block_t), void *data) { + if (size == 0) { + return 0; + } + + lfs_off_t index = lfs_ctz_index(lfs, &(lfs_off_t){size-1}); + + while (true) { + int err = cb(data, head); + if (err) { + return err; + } + + if (index == 0) { + return 0; + } + + lfs_block_t heads[2]; + int count = 2 - (index & 1); + err = lfs_bd_read(lfs, + pcache, rcache, count*sizeof(head), + head, 0, &heads, count*sizeof(head)); + heads[0] = lfs_fromle32(heads[0]); + heads[1] = lfs_fromle32(heads[1]); + if (err) { + return err; + } + + for (int i = 0; i < count-1; i++) { + err = cb(data, heads[i]); + if (err) { + return err; + } + } + + head = heads[count-1]; + index -= count; + } +} + + +/// Top level file operations /// +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *cfg) { + LFS_TRACE("lfs_file_opencfg(%p, %p, \"%s\", %x, %p {" + ".buffer=%p, .attrs=%p, .attr_count=%"PRIu32"})", + (void*)lfs, (void*)file, path, flags, + (void*)cfg, cfg->buffer, (void*)cfg->attrs, cfg->attr_count); + + // deorphan if we haven't yet, needed at most once after poweron + if ((flags & 3) != LFS_O_RDONLY) { + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_file_opencfg -> %d", err); + return err; + } + } + + // setup simple file details + int err; + file->cfg = cfg; + file->flags = flags | LFS_F_OPENED; + file->pos = 0; + file->off = 0; + file->cache.buffer = NULL; + + // allocate entry for file if it doesn't exist + lfs_stag_t tag = lfs_dir_find(lfs, &file->m, &path, &file->id); + if (tag < 0 && !(tag == LFS_ERR_NOENT && file->id != 0x3ff)) { + err = tag; + goto cleanup; + } + + // get id, add to list of mdirs to catch update changes + file->type = LFS_TYPE_REG; + file->next = (lfs_file_t*)lfs->mlist; + lfs->mlist = (struct lfs_mlist*)file; + + if (tag == LFS_ERR_NOENT) { + if (!(flags & LFS_O_CREAT)) { + err = LFS_ERR_NOENT; + goto cleanup; + } + + // check that name fits + lfs_size_t nlen = strlen(path); + if (nlen > lfs->name_max) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + // get next slot and create entry to remember name + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, file->id, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_REG, file->id, nlen), path}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), NULL})); + if (err) { + err = LFS_ERR_NAMETOOLONG; + goto cleanup; + } + + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0); + } else if (flags & LFS_O_EXCL) { + err = LFS_ERR_EXIST; + goto cleanup; + } else if (lfs_tag_type3(tag) != LFS_TYPE_REG) { + err = LFS_ERR_ISDIR; + goto cleanup; + } else if (flags & LFS_O_TRUNC) { + // truncate if requested + tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0); + file->flags |= LFS_F_DIRTY; + } else { + // try to load what's on disk, if it's inlined we'll fix it later + tag = lfs_dir_get(lfs, &file->m, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_ctz_fromle32(&file->ctz); + } + + // fetch attrs + for (unsigned i = 0; i < file->cfg->attr_count; i++) { + if ((file->flags & 3) != LFS_O_WRONLY) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + file->cfg->attrs[i].type, + file->id, file->cfg->attrs[i].size), + file->cfg->attrs[i].buffer); + if (res < 0 && res != LFS_ERR_NOENT) { + err = res; + goto cleanup; + } + } + + if ((file->flags & 3) != LFS_O_RDONLY) { + if (file->cfg->attrs[i].size > lfs->attr_max) { + err = LFS_ERR_NOSPC; + goto cleanup; + } + + file->flags |= LFS_F_DIRTY; + } + } + + // allocate buffer if needed + if (file->cfg->buffer) { + file->cache.buffer = file->cfg->buffer; + } else { + file->cache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!file->cache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leak + lfs_cache_zero(lfs, &file->cache); + + if (lfs_tag_type3(tag) == LFS_TYPE_INLINESTRUCT) { + // load inline files + file->ctz.head = LFS_BLOCK_INLINE; + file->ctz.size = lfs_tag_size(tag); + file->flags |= LFS_F_INLINE; + file->cache.block = file->ctz.head; + file->cache.off = 0; + file->cache.size = lfs->cfg->cache_size; + + // don't always read (may be new/trunc file) + if (file->ctz.size > 0) { + lfs_stag_t res = lfs_dir_get(lfs, &file->m, + LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, file->id, + lfs_min(file->cache.size, 0x3fe)), + file->cache.buffer); + if (res < 0) { + err = res; + goto cleanup; + } + } + } + + LFS_TRACE("lfs_file_opencfg -> %d", 0); + return 0; + +cleanup: + // clean up lingering resources + file->flags |= LFS_F_ERRED; + lfs_file_close(lfs, file); + LFS_TRACE("lfs_file_opencfg -> %d", err); + return err; +} + +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags) { + LFS_TRACE("lfs_file_open(%p, %p, \"%s\", %x)", + (void*)lfs, (void*)file, path, flags); + static const struct lfs_file_config defaults = {0}; + int err = lfs_file_opencfg(lfs, file, path, flags, &defaults); + LFS_TRACE("lfs_file_open -> %d", err); + return err; +} + +int lfs_file_close(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_close(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + int err = lfs_file_sync(lfs, file); + + // remove from list of mdirs + for (struct lfs_mlist **p = &lfs->mlist; *p; p = &(*p)->next) { + if (*p == (struct lfs_mlist*)file) { + *p = (*p)->next; + break; + } + } + + // clean up memory + if (!file->cfg->buffer) { + lfs_free(file->cache.buffer); + } + + file->flags &= ~LFS_F_OPENED; + LFS_TRACE("lfs_file_close -> %d", err); + return err; +} + +static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) { + LFS_ASSERT(file->flags & LFS_F_OPENED); + + while (true) { + // just relocate what exists into new block + lfs_block_t nblock; + int err = lfs_alloc(lfs, &nblock); + if (err) { + return err; + } + + err = lfs_bd_erase(lfs, nblock); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + // either read from dirty cache or disk + for (lfs_off_t i = 0; i < file->off; i++) { + uint8_t data; + if (file->flags & LFS_F_INLINE) { + err = lfs_dir_getread(lfs, &file->m, + // note we evict inline files before they can be dirty + NULL, &file->cache, file->off-i, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + i, &data, 1); + if (err) { + return err; + } + } else { + err = lfs_bd_read(lfs, + &file->cache, &lfs->rcache, file->off-i, + file->block, i, &data, 1); + if (err) { + return err; + } + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + nblock, i, &data, 1); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + } + + // copy over new state of file + memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size); + file->cache.block = lfs->pcache.block; + file->cache.off = lfs->pcache.off; + file->cache.size = lfs->pcache.size; + lfs_cache_zero(lfs, &lfs->pcache); + + file->block = nblock; + file->flags |= LFS_F_WRITING; + return 0; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, nblock); + + // just clear cache and try a new block + lfs_cache_drop(lfs, &lfs->pcache); + } +} + +static int lfs_file_outline(lfs_t *lfs, lfs_file_t *file) { + file->off = file->pos; + lfs_alloc_ack(lfs); + int err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + + file->flags &= ~LFS_F_INLINE; + return 0; +} + +static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) { + LFS_ASSERT(file->flags & LFS_F_OPENED); + + if (file->flags & LFS_F_READING) { + if (!(file->flags & LFS_F_INLINE)) { + lfs_cache_drop(lfs, &file->cache); + } + file->flags &= ~LFS_F_READING; + } + + if (file->flags & LFS_F_WRITING) { + lfs_off_t pos = file->pos; + + if (!(file->flags & LFS_F_INLINE)) { + // copy over anything after current branch + lfs_file_t orig = { + .ctz.head = file->ctz.head, + .ctz.size = file->ctz.size, + .flags = LFS_O_RDONLY | LFS_F_OPENED, + .pos = file->pos, + .cache = lfs->rcache, + }; + lfs_cache_drop(lfs, &lfs->rcache); + + while (file->pos < file->ctz.size) { + // copy over a byte at a time, leave it up to caching + // to make this efficient + uint8_t data; + lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); + if (res < 0) { + return res; + } + + res = lfs_file_write(lfs, file, &data, 1); + if (res < 0) { + return res; + } + + // keep our reference to the rcache in sync + if (lfs->rcache.block != LFS_BLOCK_NULL) { + lfs_cache_drop(lfs, &orig.cache); + lfs_cache_drop(lfs, &lfs->rcache); + } + } + + // write out what we have + while (true) { + int err = lfs_bd_flush(lfs, &file->cache, &lfs->rcache, true); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + return err; + } + + break; + +relocate: + LFS_DEBUG("Bad block at 0x%"PRIx32, file->block); + err = lfs_file_relocate(lfs, file); + if (err) { + return err; + } + } + } else { + file->pos = lfs_max(file->pos, file->ctz.size); + } + + // actual file updates + file->ctz.head = file->block; + file->ctz.size = file->pos; + file->flags &= ~LFS_F_WRITING; + file->flags |= LFS_F_DIRTY; + + file->pos = pos; + } + + return 0; +} + +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + if (file->flags & LFS_F_ERRED) { + // it's not safe to do anything if our file errored + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; + } + + int err = lfs_file_flush(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_sync -> %d", err); + return err; + } + + if ((file->flags & LFS_F_DIRTY) && + !lfs_pair_isnull(file->m.pair)) { + // update dir entry + uint16_t type; + const void *buffer; + lfs_size_t size; + struct lfs_ctz ctz; + if (file->flags & LFS_F_INLINE) { + // inline the whole file + type = LFS_TYPE_INLINESTRUCT; + buffer = file->cache.buffer; + size = file->ctz.size; + } else { + // update the ctz reference + type = LFS_TYPE_CTZSTRUCT; + // copy ctz so alloc will work during a relocate + ctz = file->ctz; + lfs_ctz_tole32(&ctz); + buffer = &ctz; + size = sizeof(ctz); + } + + // commit file data and attributes + err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( + {LFS_MKTAG(type, file->id, size), buffer}, + {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, + file->cfg->attr_count), file->cfg->attrs})); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_sync -> %d", err); + return err; + } + + file->flags &= ~LFS_F_DIRTY; + } + + LFS_TRACE("lfs_file_sync -> %d", 0); + return 0; +} + +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_file_read(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_WRONLY); + + uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->flags & LFS_F_WRITING) { + // flush out any writes + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } + + if (file->pos >= file->ctz.size) { + // eof if past end + LFS_TRACE("lfs_file_read -> %d", 0); + return 0; + } + + size = lfs_min(size, file->ctz.size - file->pos); + nsize = size; + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_READING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos, &file->block, &file->off); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_READING; + } + + // read as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + if (file->flags & LFS_F_INLINE) { + int err = lfs_dir_getread(lfs, &file->m, + NULL, &file->cache, lfs->cfg->block_size, + LFS_MKTAG(0xfff, 0x1ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0), + file->off, data, diff); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } else { + int err = lfs_bd_read(lfs, + NULL, &file->cache, lfs->cfg->block_size, + file->block, file->off, data, diff); + if (err) { + LFS_TRACE("lfs_file_read -> %d", err); + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + } + + LFS_TRACE("lfs_file_read -> %"PRId32, size); + return size; +} + +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_file_write(%p, %p, %p, %"PRIu32")", + (void*)lfs, (void*)file, buffer, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); + + const uint8_t *data = buffer; + lfs_size_t nsize = size; + + if (file->flags & LFS_F_READING) { + // drop any reads + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) { + file->pos = file->ctz.size; + } + + if (file->pos + size > lfs->file_max) { + // Larger than file limit? + LFS_TRACE("lfs_file_write -> %d", LFS_ERR_FBIG); + return LFS_ERR_FBIG; + } + + if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) { + // fill with zeros + lfs_off_t pos = file->pos; + file->pos = file->ctz.size; + + while (file->pos < pos) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + LFS_TRACE("lfs_file_write -> %"PRId32, res); + return res; + } + } + } + + if ((file->flags & LFS_F_INLINE) && + lfs_max(file->pos+nsize, file->ctz.size) > + lfs_min(0x3fe, lfs_min( + lfs->cfg->cache_size, lfs->cfg->block_size/8))) { + // inline file doesn't fit anymore + int err = lfs_file_outline(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + while (nsize > 0) { + // check if we need a new block + if (!(file->flags & LFS_F_WRITING) || + file->off == lfs->cfg->block_size) { + if (!(file->flags & LFS_F_INLINE)) { + if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { + // find out which block we're extending from + int err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + file->pos-1, &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + + // mark cache as dirty since we may have read data into it + lfs_cache_zero(lfs, &file->cache); + } + + // extend file with new blocks + lfs_alloc_ack(lfs); + int err = lfs_ctz_extend(lfs, &file->cache, &lfs->rcache, + file->block, file->pos, + &file->block, &file->off); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } else { + file->block = LFS_BLOCK_INLINE; + file->off = file->pos; + } + + file->flags |= LFS_F_WRITING; + } + + // program as much as we can in current block + lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); + while (true) { + int err = lfs_bd_prog(lfs, &file->cache, &lfs->rcache, true, + file->block, file->off, data, diff); + if (err) { + if (err == LFS_ERR_CORRUPT) { + goto relocate; + } + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + + break; +relocate: + err = lfs_file_relocate(lfs, file); + if (err) { + file->flags |= LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %d", err); + return err; + } + } + + file->pos += diff; + file->off += diff; + data += diff; + nsize -= diff; + + lfs_alloc_ack(lfs); + } + + file->flags &= ~LFS_F_ERRED; + LFS_TRACE("lfs_file_write -> %"PRId32, size); + return size; +} + +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence) { + LFS_TRACE("lfs_file_seek(%p, %p, %"PRId32", %d)", + (void*)lfs, (void*)file, off, whence); + LFS_ASSERT(file->flags & LFS_F_OPENED); + + // write out everything beforehand, may be noop if rdonly + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_seek -> %d", err); + return err; + } + + // find new pos + lfs_off_t npos = file->pos; + if (whence == LFS_SEEK_SET) { + npos = off; + } else if (whence == LFS_SEEK_CUR) { + npos = file->pos + off; + } else if (whence == LFS_SEEK_END) { + npos = file->ctz.size + off; + } + + if (npos > lfs->file_max) { + // file position out of range + LFS_TRACE("lfs_file_seek -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + // update pos + file->pos = npos; + LFS_TRACE("lfs_file_seek -> %"PRId32, npos); + return npos; +} + +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) { + LFS_TRACE("lfs_file_truncate(%p, %p, %"PRIu32")", + (void*)lfs, (void*)file, size); + LFS_ASSERT(file->flags & LFS_F_OPENED); + LFS_ASSERT((file->flags & 3) != LFS_O_RDONLY); + + if (size > LFS_FILE_MAX) { + LFS_TRACE("lfs_file_truncate -> %d", LFS_ERR_INVAL); + return LFS_ERR_INVAL; + } + + lfs_off_t pos = file->pos; + lfs_off_t oldsize = lfs_file_size(lfs, file); + if (size < oldsize) { + // need to flush since directly changing metadata + int err = lfs_file_flush(lfs, file); + if (err) { + LFS_TRACE("lfs_file_truncate -> %d", err); + return err; + } + + // lookup new head in ctz skip list + err = lfs_ctz_find(lfs, NULL, &file->cache, + file->ctz.head, file->ctz.size, + size, &file->block, &file->off); + if (err) { + LFS_TRACE("lfs_file_truncate -> %d", err); + return err; + } + + file->ctz.head = file->block; + file->ctz.size = size; + file->flags |= LFS_F_DIRTY | LFS_F_READING; + } else if (size > oldsize) { + // flush+seek if not already at end + if (file->pos != oldsize) { + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + } + + // fill with zeros + while (file->pos < size) { + lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + } + } + + // restore pos + lfs_soff_t res = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); + if (res < 0) { + LFS_TRACE("lfs_file_truncate -> %"PRId32, res); + return (int)res; + } + + LFS_TRACE("lfs_file_truncate -> %d", 0); + return 0; +} + +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_tell(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + (void)lfs; + LFS_TRACE("lfs_file_tell -> %"PRId32, file->pos); + return file->pos; +} + +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_rewind(%p, %p)", (void*)lfs, (void*)file); + lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); + if (res < 0) { + LFS_TRACE("lfs_file_rewind -> %"PRId32, res); + return (int)res; + } + + LFS_TRACE("lfs_file_rewind -> %d", 0); + return 0; +} + +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) { + LFS_TRACE("lfs_file_size(%p, %p)", (void*)lfs, (void*)file); + LFS_ASSERT(file->flags & LFS_F_OPENED); + (void)lfs; + if (file->flags & LFS_F_WRITING) { + LFS_TRACE("lfs_file_size -> %"PRId32, + lfs_max(file->pos, file->ctz.size)); + return lfs_max(file->pos, file->ctz.size); + } else { + LFS_TRACE("lfs_file_size -> %"PRId32, file->ctz.size); + return file->ctz.size; + } +} + + +/// General fs operations /// +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) { + LFS_TRACE("lfs_stat(%p, \"%s\", %p)", (void*)lfs, path, (void*)info); + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_stat -> %"PRId32, tag); + return (int)tag; + } + + int err = lfs_dir_getinfo(lfs, &cwd, lfs_tag_id(tag), info); + LFS_TRACE("lfs_stat -> %d", err); + return err; +} + +int lfs_remove(lfs_t *lfs, const char *path) { + LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path); + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0 || lfs_tag_id(tag) == 0x3ff) { + LFS_TRACE("lfs_remove -> %"PRId32, (tag < 0) ? tag : LFS_ERR_INVAL); + return (tag < 0) ? (int)tag : LFS_ERR_INVAL; + } + + struct lfs_mlist dir; + dir.next = lfs->mlist; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(tag), 8), pair); + if (res < 0) { + LFS_TRACE("lfs_remove -> %"PRId32, res); + return (int)res; + } + lfs_pair_fromle32(pair); + + err = lfs_dir_fetch(lfs, &dir.m, pair); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + if (dir.m.count > 0 || dir.m.split) { + LFS_TRACE("lfs_remove -> %d", LFS_ERR_NOTEMPTY); + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + lfs_fs_preporphans(lfs, +1); + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + dir.type = 0; + dir.id = 0; + lfs->mlist = &dir; + } + + // delete the entry + err = lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(tag), 0), NULL})); + if (err) { + lfs->mlist = dir.next; + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + lfs->mlist = dir.next; + if (lfs_tag_type3(tag) == LFS_TYPE_DIR) { + // fix orphan + lfs_fs_preporphans(lfs, -1); + + err = lfs_fs_pred(lfs, dir.m.pair, &cwd); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + + err = lfs_dir_drop(lfs, &cwd, &dir.m); + if (err) { + LFS_TRACE("lfs_remove -> %d", err); + return err; + } + } + + LFS_TRACE("lfs_remove -> %d", 0); + return 0; +} + +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { + LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath); + + // deorphan if we haven't yet, needed at most once after poweron + int err = lfs_fs_forceconsistency(lfs); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + // find old entry + lfs_mdir_t oldcwd; + lfs_stag_t oldtag = lfs_dir_find(lfs, &oldcwd, &oldpath, NULL); + if (oldtag < 0 || lfs_tag_id(oldtag) == 0x3ff) { + LFS_TRACE("lfs_rename -> %"PRId32, + (oldtag < 0) ? oldtag : LFS_ERR_INVAL); + return (oldtag < 0) ? (int)oldtag : LFS_ERR_INVAL; + } + + // find new entry + lfs_mdir_t newcwd; + uint16_t newid; + lfs_stag_t prevtag = lfs_dir_find(lfs, &newcwd, &newpath, &newid); + if ((prevtag < 0 || lfs_tag_id(prevtag) == 0x3ff) && + !(prevtag == LFS_ERR_NOENT && newid != 0x3ff)) { + LFS_TRACE("lfs_rename -> %"PRId32, + (prevtag < 0) ? prevtag : LFS_ERR_INVAL); + return (prevtag < 0) ? (int)prevtag : LFS_ERR_INVAL; + } + + // if we're in the same pair there's a few special cases... + bool samepair = (lfs_pair_cmp(oldcwd.pair, newcwd.pair) == 0); + uint16_t newoldid = lfs_tag_id(oldtag); + + struct lfs_mlist prevdir; + prevdir.next = lfs->mlist; + if (prevtag == LFS_ERR_NOENT) { + // check that name fits + lfs_size_t nlen = strlen(newpath); + if (nlen > lfs->name_max) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_NAMETOOLONG); + return LFS_ERR_NAMETOOLONG; + } + + // there is a small chance we are being renamed in the same + // directory/ to an id less than our old id, the global update + // to handle this is a bit messy + if (samepair && newid <= newoldid) { + newoldid += 1; + } + } else if (lfs_tag_type3(prevtag) != lfs_tag_type3(oldtag)) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_ISDIR); + return LFS_ERR_ISDIR; + } else if (samepair && newid == newoldid) { + // we're renaming to ourselves?? + LFS_TRACE("lfs_rename -> %d", 0); + return 0; + } else if (lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // must be empty before removal + lfs_block_t prevpair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &newcwd, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair); + if (res < 0) { + LFS_TRACE("lfs_rename -> %"PRId32, res); + return (int)res; + } + lfs_pair_fromle32(prevpair); + + // must be empty before removal + err = lfs_dir_fetch(lfs, &prevdir.m, prevpair); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + if (prevdir.m.count > 0 || prevdir.m.split) { + LFS_TRACE("lfs_rename -> %d", LFS_ERR_NOTEMPTY); + return LFS_ERR_NOTEMPTY; + } + + // mark fs as orphaned + lfs_fs_preporphans(lfs, +1); + + // I know it's crazy but yes, dir can be changed by our parent's + // commit (if predecessor is child) + prevdir.type = 0; + prevdir.id = 0; + lfs->mlist = &prevdir; + } + + if (!samepair) { + lfs_fs_prepmove(lfs, newoldid, oldcwd.pair); + } + + // move over all attributes + err = lfs_dir_commit(lfs, &newcwd, LFS_MKATTRS( + {LFS_MKTAG_IF(prevtag != LFS_ERR_NOENT, + LFS_TYPE_DELETE, newid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_CREATE, newid, 0), NULL}, + {LFS_MKTAG(lfs_tag_type3(oldtag), newid, strlen(newpath)), newpath}, + {LFS_MKTAG(LFS_FROM_MOVE, newid, lfs_tag_id(oldtag)), &oldcwd}, + {LFS_MKTAG_IF(samepair, + LFS_TYPE_DELETE, newoldid, 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + // let commit clean up after move (if we're different! otherwise move + // logic already fixed it for us) + if (!samepair && lfs_gstate_hasmove(&lfs->gstate)) { + // prep gstate and delete move id + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &oldcwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(oldtag), 0), NULL})); + if (err) { + lfs->mlist = prevdir.next; + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + } + + lfs->mlist = prevdir.next; + if (prevtag != LFS_ERR_NOENT && lfs_tag_type3(prevtag) == LFS_TYPE_DIR) { + // fix orphan + lfs_fs_preporphans(lfs, -1); + + err = lfs_fs_pred(lfs, prevdir.m.pair, &newcwd); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + + err = lfs_dir_drop(lfs, &newcwd, &prevdir.m); + if (err) { + LFS_TRACE("lfs_rename -> %d", err); + return err; + } + } + + LFS_TRACE("lfs_rename -> %d", 0); + return 0; +} + +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_getattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + LFS_TRACE("lfs_getattr -> %"PRId32, tag); + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + LFS_TRACE("lfs_getattr -> %d", err); + return err; + } + } + + tag = lfs_dir_get(lfs, &cwd, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_USERATTR + type, + id, lfs_min(size, lfs->attr_max)), + buffer); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + LFS_TRACE("lfs_getattr -> %d", LFS_ERR_NOATTR); + return LFS_ERR_NOATTR; + } + + LFS_TRACE("lfs_getattr -> %"PRId32, tag); + return tag; + } + + size = lfs_tag_size(tag); + LFS_TRACE("lfs_getattr -> %"PRId32, size); + return size; +} + +static int lfs_commitattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + lfs_mdir_t cwd; + lfs_stag_t tag = lfs_dir_find(lfs, &cwd, &path, NULL); + if (tag < 0) { + return tag; + } + + uint16_t id = lfs_tag_id(tag); + if (id == 0x3ff) { + // special case for root + id = 0; + int err = lfs_dir_fetch(lfs, &cwd, lfs->root); + if (err) { + return err; + } + } + + return lfs_dir_commit(lfs, &cwd, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_USERATTR + type, id, size), buffer})); +} + +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size) { + LFS_TRACE("lfs_setattr(%p, \"%s\", %"PRIu8", %p, %"PRIu32")", + (void*)lfs, path, type, buffer, size); + if (size > lfs->attr_max) { + LFS_TRACE("lfs_setattr -> %d", LFS_ERR_NOSPC); + return LFS_ERR_NOSPC; + } + + int err = lfs_commitattr(lfs, path, type, buffer, size); + LFS_TRACE("lfs_setattr -> %d", err); + return err; +} + +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type) { + LFS_TRACE("lfs_removeattr(%p, \"%s\", %"PRIu8")", (void*)lfs, path, type); + int err = lfs_commitattr(lfs, path, type, NULL, 0x3ff); + LFS_TRACE("lfs_removeattr -> %d", err); + return err; +} + + +/// Filesystem operations /// +static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { + lfs->cfg = cfg; + int err = 0; + + // validate that the lfs-cfg sizes were initiated properly before + // performing any arithmetic logics with them + LFS_ASSERT(lfs->cfg->read_size != 0); + LFS_ASSERT(lfs->cfg->prog_size != 0); + LFS_ASSERT(lfs->cfg->cache_size != 0); + + // check that block size is a multiple of cache size is a multiple + // of prog and read sizes + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0); + LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0); + LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_size == 0); + + // check that the block size is large enough to fit ctz pointers + LFS_ASSERT(4*lfs_npw2(0xffffffff / (lfs->cfg->block_size-2*4)) + <= lfs->cfg->block_size); + + // block_cycles = 0 is no longer supported. + // + // block_cycles is the number of erase cycles before littlefs evicts + // metadata logs as a part of wear leveling. Suggested values are in the + // range of 100-1000, or set block_cycles to -1 to disable block-level + // wear-leveling. + LFS_ASSERT(lfs->cfg->block_cycles != 0); + + + // setup read cache + if (lfs->cfg->read_buffer) { + lfs->rcache.buffer = lfs->cfg->read_buffer; + } else { + lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->rcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // setup program cache + if (lfs->cfg->prog_buffer) { + lfs->pcache.buffer = lfs->cfg->prog_buffer; + } else { + lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size); + if (!lfs->pcache.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // zero to avoid information leaks + lfs_cache_zero(lfs, &lfs->rcache); + lfs_cache_zero(lfs, &lfs->pcache); + + // setup lookahead, must be multiple of 64-bits, 32-bit aligned + LFS_ASSERT(lfs->cfg->lookahead_size > 0); + LFS_ASSERT(lfs->cfg->lookahead_size % 8 == 0 && + (uintptr_t)lfs->cfg->lookahead_buffer % 4 == 0); + if (lfs->cfg->lookahead_buffer) { + lfs->free.buffer = lfs->cfg->lookahead_buffer; + } else { + lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead_size); + if (!lfs->free.buffer) { + err = LFS_ERR_NOMEM; + goto cleanup; + } + } + + // check that the size limits are sane + LFS_ASSERT(lfs->cfg->name_max <= LFS_NAME_MAX); + lfs->name_max = lfs->cfg->name_max; + if (!lfs->name_max) { + lfs->name_max = LFS_NAME_MAX; + } + + LFS_ASSERT(lfs->cfg->file_max <= LFS_FILE_MAX); + lfs->file_max = lfs->cfg->file_max; + if (!lfs->file_max) { + lfs->file_max = LFS_FILE_MAX; + } + + LFS_ASSERT(lfs->cfg->attr_max <= LFS_ATTR_MAX); + lfs->attr_max = lfs->cfg->attr_max; + if (!lfs->attr_max) { + lfs->attr_max = LFS_ATTR_MAX; + } + + // setup default state + lfs->root[0] = LFS_BLOCK_NULL; + lfs->root[1] = LFS_BLOCK_NULL; + lfs->mlist = NULL; + lfs->seed = 0; + lfs->gdisk = (lfs_gstate_t){0}; + lfs->gstate = (lfs_gstate_t){0}; + lfs->gdelta = (lfs_gstate_t){0}; +#ifdef LFS_MIGRATE + lfs->lfs1 = NULL; +#endif + + return 0; + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs_deinit(lfs_t *lfs) { + // free allocated memory + if (!lfs->cfg->read_buffer) { + lfs_free(lfs->rcache.buffer); + } + + if (!lfs->cfg->prog_buffer) { + lfs_free(lfs->pcache.buffer); + } + + if (!lfs->cfg->lookahead_buffer) { + lfs_free(lfs->free.buffer); + } + + return 0; +} + +int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_format(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + LFS_TRACE("lfs_format -> %d", err); + return err; + } + + // create free lookahead + memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); + lfs->free.off = 0; + lfs->free.size = lfs_min(8*lfs->cfg->lookahead_size, + lfs->cfg->block_count); + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // create root dir + lfs_mdir_t root; + err = lfs_dir_alloc(lfs, &root); + if (err) { + goto cleanup; + } + + // write one superblock + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &root, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &root, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting any + // older version of littlefs that may live on disk + root.erased = false; + err = lfs_dir_commit(lfs, &root, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs_deinit(lfs); + LFS_TRACE("lfs_format -> %d", err); + return err; +} + +int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_mount(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + int err = lfs_init(lfs, cfg); + if (err) { + LFS_TRACE("lfs_mount -> %d", err); + return err; + } + + // scan directory blocks for superblock and any global updates + lfs_mdir_t dir = {.tail = {0, 1}}; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + err = LFS_ERR_CORRUPT; + goto cleanup; + } + cycle += 1; + + // fetch next block in tail list + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, &dir, dir.tail, + LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), + NULL, + lfs_dir_find_match, &(struct lfs_dir_find_match){ + lfs, "littlefs", 8}); + if (tag < 0) { + err = tag; + goto cleanup; + } + + // has superblock? + if (tag && !lfs_tag_isdelete(tag)) { + // update root + lfs->root[0] = dir.pair[0]; + lfs->root[1] = dir.pair[1]; + + // grab superblock + lfs_superblock_t superblock; + tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x7ff, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock); + if (tag < 0) { + err = tag; + goto cleanup; + } + lfs_superblock_fromle32(&superblock); + + // check version + uint16_t major_version = (0xffff & (superblock.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.version >> 0)); + if ((major_version != LFS_DISK_VERSION_MAJOR || + minor_version > LFS_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%"PRIu16".%"PRIu16, + major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + // check superblock configuration + if (superblock.name_max) { + if (superblock.name_max > lfs->name_max) { + LFS_ERROR("Unsupported name_max (%"PRIu32" > %"PRIu32")", + superblock.name_max, lfs->name_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->name_max = superblock.name_max; + } + + if (superblock.file_max) { + if (superblock.file_max > lfs->file_max) { + LFS_ERROR("Unsupported file_max (%"PRIu32" > %"PRIu32")", + superblock.file_max, lfs->file_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->file_max = superblock.file_max; + } + + if (superblock.attr_max) { + if (superblock.attr_max > lfs->attr_max) { + LFS_ERROR("Unsupported attr_max (%"PRIu32" > %"PRIu32")", + superblock.attr_max, lfs->attr_max); + err = LFS_ERR_INVAL; + goto cleanup; + } + + lfs->attr_max = superblock.attr_max; + } + } + + // has gstate? + err = lfs_dir_getgstate(lfs, &dir, &lfs->gstate); + if (err) { + goto cleanup; + } + } + + // found superblock? + if (lfs_pair_isnull(lfs->root)) { + err = LFS_ERR_INVAL; + goto cleanup; + } + + // update littlefs with gstate + if (!lfs_gstate_iszero(&lfs->gstate)) { + LFS_DEBUG("Found pending gstate 0x%08"PRIx32"%08"PRIx32"%08"PRIx32, + lfs->gstate.tag, + lfs->gstate.pair[0], + lfs->gstate.pair[1]); + } + lfs->gstate.tag += !lfs_tag_isvalid(lfs->gstate.tag); + lfs->gdisk = lfs->gstate; + + // setup free lookahead + lfs_alloc_reset(lfs); + + LFS_TRACE("lfs_mount -> %d", 0); + return 0; + +cleanup: + lfs_unmount(lfs); + LFS_TRACE("lfs_mount -> %d", err); + return err; +} + +int lfs_unmount(lfs_t *lfs) { + LFS_TRACE("lfs_unmount(%p)", (void*)lfs); + int err = lfs_deinit(lfs); + LFS_TRACE("lfs_unmount -> %d", err); + return err; +} + + +/// Filesystem filesystem operations /// +int lfs_fs_traverseraw(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data, + bool includeorphans) { + // iterate over metadata pairs + lfs_mdir_t dir = {.tail = {0, 1}}; + +#ifdef LFS_MIGRATE + // also consider v1 blocks during migration + if (lfs->lfs1) { + int err = lfs1_traverse(lfs, cb, data); + if (err) { + return err; + } + + dir.tail[0] = lfs->root[0]; + dir.tail[1] = lfs->root[1]; + } +#endif + + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(dir.tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + for (int i = 0; i < 2; i++) { + int err = cb(data, dir.tail[i]); + if (err) { + return err; + } + } + + // iterate through ids in directory + int err = lfs_dir_fetch(lfs, &dir, dir.tail); + if (err) { + return err; + } + + for (uint16_t id = 0; id < dir.count; id++) { + struct lfs_ctz ctz; + lfs_stag_t tag = lfs_dir_get(lfs, &dir, LFS_MKTAG(0x700, 0x3ff, 0), + LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz); + if (tag < 0) { + if (tag == LFS_ERR_NOENT) { + continue; + } + return tag; + } + lfs_ctz_fromle32(&ctz); + + if (lfs_tag_type3(tag) == LFS_TYPE_CTZSTRUCT) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + ctz.head, ctz.size, cb, data); + if (err) { + return err; + } + } else if (includeorphans && + lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { + for (int i = 0; i < 2; i++) { + err = cb(data, (&ctz.head)[i]); + if (err) { + return err; + } + } + } + } + } + + // iterate over any open files + for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) { + if (f->type != LFS_TYPE_REG) { + continue; + } + + if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->ctz.head, f->ctz.size, cb, data); + if (err) { + return err; + } + } + + if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) { + int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, + f->block, f->pos, cb, data); + if (err) { + return err; + } + } + } + + return 0; +} + +int lfs_fs_traverse(lfs_t *lfs, + int (*cb)(void *data, lfs_block_t block), void *data) { + LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", + (void*)lfs, (void*)(uintptr_t)cb, data); + int err = lfs_fs_traverseraw(lfs, cb, data, true); + LFS_TRACE("lfs_fs_traverse -> %d", 0); + return err; +} + +static int lfs_fs_pred(lfs_t *lfs, + const lfs_block_t pair[2], lfs_mdir_t *pdir) { + // iterate over all directory directory entries + pdir->tail[0] = 0; + pdir->tail[1] = 1; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(pdir->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + if (lfs_pair_cmp(pdir->tail, pair) == 0) { + return 0; + } + + int err = lfs_dir_fetch(lfs, pdir, pdir->tail); + if (err) { + return err; + } + } + + return LFS_ERR_NOENT; +} + +struct lfs_fs_parent_match { + lfs_t *lfs; + const lfs_block_t pair[2]; +}; + +static int lfs_fs_parent_match(void *data, + lfs_tag_t tag, const void *buffer) { + struct lfs_fs_parent_match *find = data; + lfs_t *lfs = find->lfs; + const struct lfs_diskoff *disk = buffer; + (void)tag; + + lfs_block_t child[2]; + int err = lfs_bd_read(lfs, + &lfs->pcache, &lfs->rcache, lfs->cfg->block_size, + disk->block, disk->off, &child, sizeof(child)); + if (err) { + return err; + } + + lfs_pair_fromle32(child); + return (lfs_pair_cmp(child, find->pair) == 0) ? LFS_CMP_EQ : LFS_CMP_LT; +} + +static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], + lfs_mdir_t *parent) { + // use fetchmatch with callback to find pairs + parent->tail[0] = 0; + parent->tail[1] = 1; + lfs_block_t cycle = 0; + while (!lfs_pair_isnull(parent->tail)) { + if (cycle >= lfs->cfg->block_count/2) { + // loop detected + return LFS_ERR_CORRUPT; + } + cycle += 1; + + lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, + LFS_MKTAG(0x7ff, 0, 0x3ff), + LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), + NULL, + lfs_fs_parent_match, &(struct lfs_fs_parent_match){ + lfs, {pair[0], pair[1]}}); + if (tag && tag != LFS_ERR_NOENT) { + return tag; + } + } + + return LFS_ERR_NOENT; +} + +static int lfs_fs_relocate(lfs_t *lfs, + const lfs_block_t oldpair[2], lfs_block_t newpair[2]) { + // update internal root + if (lfs_pair_cmp(oldpair, lfs->root) == 0) { + lfs->root[0] = newpair[0]; + lfs->root[1] = newpair[1]; + } + + // update internally tracked dirs + for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { + if (lfs_pair_cmp(oldpair, d->m.pair) == 0) { + d->m.pair[0] = newpair[0]; + d->m.pair[1] = newpair[1]; + } + + if (d->type == LFS_TYPE_DIR && + lfs_pair_cmp(oldpair, ((lfs_dir_t*)d)->head) == 0) { + ((lfs_dir_t*)d)->head[0] = newpair[0]; + ((lfs_dir_t*)d)->head[1] = newpair[1]; + } + } + + // find parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, oldpair, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (tag != LFS_ERR_NOENT) { + // update disk, this creates a desync + lfs_fs_preporphans(lfs, +1); + + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + if (moveid < lfs_tag_id(tag)) { + tag -= LFS_MKTAG(0, 1, 0); + } + } + + lfs_pair_tole32(newpair); + int err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {tag, newpair})); + lfs_pair_fromle32(newpair); + if (err) { + return err; + } + + // next step, clean up orphans + lfs_fs_preporphans(lfs, -1); + } + + // find pred + int err = lfs_fs_pred(lfs, oldpair, &parent); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + // if we can't find dir, it must be new + if (err != LFS_ERR_NOENT) { + // fix pending move in this pair? this looks like an optimization but + // is in fact _required_ since relocating may outdate the move. + uint16_t moveid = 0x3ff; + if (lfs_gstate_hasmovehere(&lfs->gstate, parent.pair)) { + moveid = lfs_tag_id(lfs->gstate.tag); + LFS_DEBUG("Fixing move while relocating " + "{0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16"\n", + parent.pair[0], parent.pair[1], moveid); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + } + + // replace bad pair, either we clean up desync, or no desync occured + lfs_pair_tole32(newpair); + err = lfs_dir_commit(lfs, &parent, LFS_MKATTRS( + {LFS_MKTAG_IF(moveid != 0x3ff, + LFS_TYPE_DELETE, moveid, 0), NULL}, + {LFS_MKTAG(LFS_TYPE_TAIL + parent.split, 0x3ff, 8), newpair})); + lfs_pair_fromle32(newpair); + if (err) { + return err; + } + } + + return 0; +} + +static void lfs_fs_preporphans(lfs_t *lfs, int8_t orphans) { + LFS_ASSERT(lfs_tag_size(lfs->gstate.tag) > 0 || orphans >= 0); + lfs->gstate.tag += orphans; + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x800, 0, 0)) | + ((uint32_t)lfs_gstate_hasorphans(&lfs->gstate) << 31)); +} + +static void lfs_fs_prepmove(lfs_t *lfs, + uint16_t id, const lfs_block_t pair[2]) { + lfs->gstate.tag = ((lfs->gstate.tag & ~LFS_MKTAG(0x7ff, 0x3ff, 0)) | + ((id != 0x3ff) ? LFS_MKTAG(LFS_TYPE_DELETE, id, 0) : 0)); + lfs->gstate.pair[0] = (id != 0x3ff) ? pair[0] : 0; + lfs->gstate.pair[1] = (id != 0x3ff) ? pair[1] : 0; +} + +static int lfs_fs_demove(lfs_t *lfs) { + if (!lfs_gstate_hasmove(&lfs->gdisk)) { + return 0; + } + + // Fix bad moves + LFS_DEBUG("Fixing move {0x%"PRIx32", 0x%"PRIx32"} 0x%"PRIx16, + lfs->gdisk.pair[0], + lfs->gdisk.pair[1], + lfs_tag_id(lfs->gdisk.tag)); + + // fetch and delete the moved entry + lfs_mdir_t movedir; + int err = lfs_dir_fetch(lfs, &movedir, lfs->gdisk.pair); + if (err) { + return err; + } + + // prep gstate and delete move id + uint16_t moveid = lfs_tag_id(lfs->gdisk.tag); + lfs_fs_prepmove(lfs, 0x3ff, NULL); + err = lfs_dir_commit(lfs, &movedir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_DELETE, moveid, 0), NULL})); + if (err) { + return err; + } + + return 0; +} + +static int lfs_fs_deorphan(lfs_t *lfs) { + if (!lfs_gstate_hasorphans(&lfs->gstate)) { + return 0; + } + + // Fix any orphans + lfs_mdir_t pdir = {.split = true, .tail = {0, 1}}; + lfs_mdir_t dir; + + // iterate over all directory directory entries + while (!lfs_pair_isnull(pdir.tail)) { + int err = lfs_dir_fetch(lfs, &dir, pdir.tail); + if (err) { + return err; + } + + // check head blocks for orphans + if (!pdir.split) { + // check if we have a parent + lfs_mdir_t parent; + lfs_stag_t tag = lfs_fs_parent(lfs, pdir.tail, &parent); + if (tag < 0 && tag != LFS_ERR_NOENT) { + return tag; + } + + if (tag == LFS_ERR_NOENT) { + // we are an orphan + LFS_DEBUG("Fixing orphan {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1]); + + err = lfs_dir_drop(lfs, &pdir, &dir); + if (err) { + return err; + } + + // refetch tail + continue; + } + + lfs_block_t pair[2]; + lfs_stag_t res = lfs_dir_get(lfs, &parent, + LFS_MKTAG(0x7ff, 0x3ff, 0), tag, pair); + if (res < 0) { + return res; + } + lfs_pair_fromle32(pair); + + if (!lfs_pair_sync(pair, pdir.tail)) { + // we have desynced + LFS_DEBUG("Fixing half-orphan {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + pdir.tail[0], pdir.tail[1], pair[0], pair[1]); + + lfs_pair_tole32(pair); + err = lfs_dir_commit(lfs, &pdir, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), pair})); + lfs_pair_fromle32(pair); + if (err) { + return err; + } + + // refetch tail + continue; + } + } + + pdir = dir; + } + + // mark orphans as fixed + lfs_fs_preporphans(lfs, -lfs_gstate_getorphans(&lfs->gstate)); + return 0; +} + +static int lfs_fs_forceconsistency(lfs_t *lfs) { + int err = lfs_fs_demove(lfs); + if (err) { + return err; + } + + err = lfs_fs_deorphan(lfs); + if (err) { + return err; + } + + return 0; +} + +static int lfs_fs_size_count(void *p, lfs_block_t block) { + (void)block; + lfs_size_t *size = p; + *size += 1; + return 0; +} + +lfs_ssize_t lfs_fs_size(lfs_t *lfs) { + LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); + lfs_size_t size = 0; + int err = lfs_fs_traverseraw(lfs, lfs_fs_size_count, &size, false); + if (err) { + LFS_TRACE("lfs_fs_size -> %d", err); + return err; + } + + LFS_TRACE("lfs_fs_size -> %d", err); + return size; +} + +#ifdef LFS_MIGRATE +////// Migration from littelfs v1 below this ////// + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_VERSION 0x00010007 +#define LFS1_VERSION_MAJOR (0xffff & (LFS1_VERSION >> 16)) +#define LFS1_VERSION_MINOR (0xffff & (LFS1_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS1_DISK_VERSION 0x00010001 +#define LFS1_DISK_VERSION_MAJOR (0xffff & (LFS1_DISK_VERSION >> 16)) +#define LFS1_DISK_VERSION_MINOR (0xffff & (LFS1_DISK_VERSION >> 0)) + + +/// v1 Definitions /// + +// File types +enum lfs1_type { + LFS1_TYPE_REG = 0x11, + LFS1_TYPE_DIR = 0x22, + LFS1_TYPE_SUPERBLOCK = 0x2e, +}; + +typedef struct lfs1 { + lfs_block_t root[2]; +} lfs1_t; + +typedef struct lfs1_entry { + lfs_off_t off; + + struct lfs1_disk_entry { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + union { + struct { + lfs_block_t head; + lfs_size_t size; + } file; + lfs_block_t dir[2]; + } u; + } d; +} lfs1_entry_t; + +typedef struct lfs1_dir { + struct lfs1_dir *next; + lfs_block_t pair[2]; + lfs_off_t off; + + lfs_block_t head[2]; + lfs_off_t pos; + + struct lfs1_disk_dir { + uint32_t rev; + lfs_size_t size; + lfs_block_t tail[2]; + } d; +} lfs1_dir_t; + +typedef struct lfs1_superblock { + lfs_off_t off; + + struct lfs1_disk_superblock { + uint8_t type; + uint8_t elen; + uint8_t alen; + uint8_t nlen; + lfs_block_t root[2]; + uint32_t block_size; + uint32_t block_count; + uint32_t version; + char magic[8]; + } d; +} lfs1_superblock_t; + + +/// Low-level wrappers v1->v2 /// +static void lfs1_crc(uint32_t *crc, const void *buffer, size_t size) { + *crc = lfs_crc(*crc, buffer, size); +} + +static int lfs1_bd_read(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + // if we ever do more than writes to alternating pairs, + // this may need to consider pcache + return lfs_bd_read(lfs, &lfs->pcache, &lfs->rcache, size, + block, off, buffer, size); +} + +static int lfs1_bd_crc(lfs_t *lfs, lfs_block_t block, + lfs_off_t off, lfs_size_t size, uint32_t *crc) { + for (lfs_off_t i = 0; i < size; i++) { + uint8_t c; + int err = lfs1_bd_read(lfs, block, off+i, &c, 1); + if (err) { + return err; + } + + lfs1_crc(crc, &c, 1); + } + + return 0; +} + + +/// Endian swapping functions /// +static void lfs1_dir_fromle32(struct lfs1_disk_dir *d) { + d->rev = lfs_fromle32(d->rev); + d->size = lfs_fromle32(d->size); + d->tail[0] = lfs_fromle32(d->tail[0]); + d->tail[1] = lfs_fromle32(d->tail[1]); +} + +static void lfs1_dir_tole32(struct lfs1_disk_dir *d) { + d->rev = lfs_tole32(d->rev); + d->size = lfs_tole32(d->size); + d->tail[0] = lfs_tole32(d->tail[0]); + d->tail[1] = lfs_tole32(d->tail[1]); +} + +static void lfs1_entry_fromle32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_fromle32(d->u.dir[0]); + d->u.dir[1] = lfs_fromle32(d->u.dir[1]); +} + +static void lfs1_entry_tole32(struct lfs1_disk_entry *d) { + d->u.dir[0] = lfs_tole32(d->u.dir[0]); + d->u.dir[1] = lfs_tole32(d->u.dir[1]); +} + +static void lfs1_superblock_fromle32(struct lfs1_disk_superblock *d) { + d->root[0] = lfs_fromle32(d->root[0]); + d->root[1] = lfs_fromle32(d->root[1]); + d->block_size = lfs_fromle32(d->block_size); + d->block_count = lfs_fromle32(d->block_count); + d->version = lfs_fromle32(d->version); +} + + +///// Metadata pair and directory operations /// +static inline lfs_size_t lfs1_entry_size(const lfs1_entry_t *entry) { + return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; +} + +static int lfs1_dir_fetch(lfs_t *lfs, + lfs1_dir_t *dir, const lfs_block_t pair[2]) { + // copy out pair, otherwise may be aliasing dir + const lfs_block_t tpair[2] = {pair[0], pair[1]}; + bool valid = false; + + // check both blocks for the most recent revision + for (int i = 0; i < 2; i++) { + struct lfs1_disk_dir test; + int err = lfs1_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { + continue; + } + + if ((0x7fffffff & test.size) < sizeof(test)+4 || + (0x7fffffff & test.size) > lfs->cfg->block_size) { + continue; + } + + uint32_t crc = 0xffffffff; + lfs1_dir_tole32(&test); + lfs1_crc(&crc, &test, sizeof(test)); + lfs1_dir_fromle32(&test); + err = lfs1_bd_crc(lfs, tpair[i], sizeof(test), + (0x7fffffff & test.size) - sizeof(test), &crc); + if (err) { + if (err == LFS_ERR_CORRUPT) { + continue; + } + return err; + } + + if (crc != 0) { + continue; + } + + valid = true; + + // setup dir in case it's valid + dir->pair[0] = tpair[(i+0) % 2]; + dir->pair[1] = tpair[(i+1) % 2]; + dir->off = sizeof(dir->d); + dir->d = test; + } + + if (!valid) { + LFS_ERROR("Corrupted dir pair at {0x%"PRIx32", 0x%"PRIx32"}", + tpair[0], tpair[1]); + return LFS_ERR_CORRUPT; + } + + return 0; +} + +static int lfs1_dir_next(lfs_t *lfs, lfs1_dir_t *dir, lfs1_entry_t *entry) { + while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size)-4) { + if (!(0x80000000 & dir->d.size)) { + entry->off = dir->off; + return LFS_ERR_NOENT; + } + + int err = lfs1_dir_fetch(lfs, dir, dir->d.tail); + if (err) { + return err; + } + + dir->off = sizeof(dir->d); + dir->pos += sizeof(dir->d) + 4; + } + + int err = lfs1_bd_read(lfs, dir->pair[0], dir->off, + &entry->d, sizeof(entry->d)); + lfs1_entry_fromle32(&entry->d); + if (err) { + return err; + } + + entry->off = dir->off; + dir->off += lfs1_entry_size(entry); + dir->pos += lfs1_entry_size(entry); + return 0; +} + +/// littlefs v1 specific operations /// +int lfs1_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // iterate over metadata pairs + lfs1_dir_t dir; + lfs1_entry_t entry; + lfs_block_t cwd[2] = {0, 1}; + + while (true) { + for (int i = 0; i < 2; i++) { + int err = cb(data, cwd[i]); + if (err) { + return err; + } + } + + int err = lfs1_dir_fetch(lfs, &dir, cwd); + if (err) { + return err; + } + + // iterate over contents + while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size)-4) { + err = lfs1_bd_read(lfs, dir.pair[0], dir.off, + &entry.d, sizeof(entry.d)); + lfs1_entry_fromle32(&entry.d); + if (err) { + return err; + } + + dir.off += lfs1_entry_size(&entry); + if ((0x70 & entry.d.type) == (0x70 & LFS1_TYPE_REG)) { + err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, + entry.d.u.file.head, entry.d.u.file.size, cb, data); + if (err) { + return err; + } + } + } + + // we also need to check if we contain a threaded v2 directory + lfs_mdir_t dir2 = {.split=true, .tail={cwd[0], cwd[1]}}; + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + break; + } + + for (int i = 0; i < 2; i++) { + err = cb(data, dir2.pair[i]); + if (err) { + return err; + } + } + } + + cwd[0] = dir.d.tail[0]; + cwd[1] = dir.d.tail[1]; + + if (lfs_pair_isnull(cwd)) { + break; + } + } + + return 0; +} + +static int lfs1_moved(lfs_t *lfs, const void *e) { + if (lfs_pair_isnull(lfs->lfs1->root)) { + return 0; + } + + // skip superblock + lfs1_dir_t cwd; + int err = lfs1_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1}); + if (err) { + return err; + } + + // iterate over all directory directory entries + lfs1_entry_t entry; + while (!lfs_pair_isnull(cwd.d.tail)) { + err = lfs1_dir_fetch(lfs, &cwd, cwd.d.tail); + if (err) { + return err; + } + + while (true) { + err = lfs1_dir_next(lfs, &cwd, &entry); + if (err && err != LFS_ERR_NOENT) { + return err; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + if (!(0x80 & entry.d.type) && + memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { + return true; + } + } + } + + return false; +} + +/// Filesystem operations /// +static int lfs1_mount(lfs_t *lfs, struct lfs1 *lfs1, + const struct lfs_config *cfg) { + int err = 0; + { + err = lfs_init(lfs, cfg); + if (err) { + return err; + } + + lfs->lfs1 = lfs1; + lfs->lfs1->root[0] = LFS_BLOCK_NULL; + lfs->lfs1->root[1] = LFS_BLOCK_NULL; + + // setup free lookahead + lfs->free.off = 0; + lfs->free.size = 0; + lfs->free.i = 0; + lfs_alloc_ack(lfs); + + // load superblock + lfs1_dir_t dir; + lfs1_superblock_t superblock; + err = lfs1_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); + if (err && err != LFS_ERR_CORRUPT) { + goto cleanup; + } + + if (!err) { + err = lfs1_bd_read(lfs, dir.pair[0], sizeof(dir.d), + &superblock.d, sizeof(superblock.d)); + lfs1_superblock_fromle32(&superblock.d); + if (err) { + goto cleanup; + } + + lfs->lfs1->root[0] = superblock.d.root[0]; + lfs->lfs1->root[1] = superblock.d.root[1]; + } + + if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { + LFS_ERROR("Invalid superblock at {0x%"PRIx32", 0x%"PRIx32"}", + 0, 1); + err = LFS_ERR_CORRUPT; + goto cleanup; + } + + uint16_t major_version = (0xffff & (superblock.d.version >> 16)); + uint16_t minor_version = (0xffff & (superblock.d.version >> 0)); + if ((major_version != LFS1_DISK_VERSION_MAJOR || + minor_version > LFS1_DISK_VERSION_MINOR)) { + LFS_ERROR("Invalid version v%d.%d", major_version, minor_version); + err = LFS_ERR_INVAL; + goto cleanup; + } + + return 0; + } + +cleanup: + lfs_deinit(lfs); + return err; +} + +static int lfs1_unmount(lfs_t *lfs) { + return lfs_deinit(lfs); +} + +/// v1 migration /// +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg) { + LFS_TRACE("lfs_migrate(%p, %p {.context=%p, " + ".read=%p, .prog=%p, .erase=%p, .sync=%p, " + ".read_size=%"PRIu32", .prog_size=%"PRIu32", " + ".block_size=%"PRIu32", .block_count=%"PRIu32", " + ".block_cycles=%"PRIu32", .cache_size=%"PRIu32", " + ".lookahead_size=%"PRIu32", .read_buffer=%p, " + ".prog_buffer=%p, .lookahead_buffer=%p, " + ".name_max=%"PRIu32", .file_max=%"PRIu32", " + ".attr_max=%"PRIu32"})", + (void*)lfs, (void*)cfg, cfg->context, + (void*)(uintptr_t)cfg->read, (void*)(uintptr_t)cfg->prog, + (void*)(uintptr_t)cfg->erase, (void*)(uintptr_t)cfg->sync, + cfg->read_size, cfg->prog_size, cfg->block_size, cfg->block_count, + cfg->block_cycles, cfg->cache_size, cfg->lookahead_size, + cfg->read_buffer, cfg->prog_buffer, cfg->lookahead_buffer, + cfg->name_max, cfg->file_max, cfg->attr_max); + struct lfs1 lfs1; + int err = lfs1_mount(lfs, &lfs1, cfg); + if (err) { + LFS_TRACE("lfs_migrate -> %d", err); + return err; + } + + { + // iterate through each directory, copying over entries + // into new directory + lfs1_dir_t dir1; + lfs_mdir_t dir2; + dir1.d.tail[0] = lfs->lfs1->root[0]; + dir1.d.tail[1] = lfs->lfs1->root[1]; + while (!lfs_pair_isnull(dir1.d.tail)) { + // iterate old dir + err = lfs1_dir_fetch(lfs, &dir1, dir1.d.tail); + if (err) { + goto cleanup; + } + + // create new dir and bind as temporary pretend root + err = lfs_dir_alloc(lfs, &dir2); + if (err) { + goto cleanup; + } + + dir2.rev = dir1.d.rev; + dir1.head[0] = dir1.pair[0]; + dir1.head[1] = dir1.pair[1]; + lfs->root[0] = dir2.pair[0]; + lfs->root[1] = dir2.pair[1]; + + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + + while (true) { + lfs1_entry_t entry1; + err = lfs1_dir_next(lfs, &dir1, &entry1); + if (err && err != LFS_ERR_NOENT) { + goto cleanup; + } + + if (err == LFS_ERR_NOENT) { + break; + } + + // check that entry has not been moved + if (entry1.d.type & 0x80) { + int moved = lfs1_moved(lfs, &entry1.d.u); + if (moved < 0) { + err = moved; + goto cleanup; + } + + if (moved) { + continue; + } + + entry1.d.type &= ~0x80; + } + + // also fetch name + char name[LFS_NAME_MAX+1]; + memset(name, 0, sizeof(name)); + err = lfs1_bd_read(lfs, dir1.pair[0], + entry1.off + 4+entry1.d.elen+entry1.d.alen, + name, entry1.d.nlen); + if (err) { + goto cleanup; + } + + bool isdir = (entry1.d.type == LFS1_TYPE_DIR); + + // create entry in new dir + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + uint16_t id; + err = lfs_dir_find(lfs, &dir2, &(const char*){name}, &id); + if (!(err == LFS_ERR_NOENT && id != 0x3ff)) { + err = (err < 0) ? err : LFS_ERR_EXIST; + goto cleanup; + } + + lfs1_entry_tole32(&entry1.d); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, id, 0)}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIR, id, entry1.d.nlen, + LFS_TYPE_REG, id, entry1.d.nlen), + name}, + {LFS_MKTAG_IF_ELSE(isdir, + LFS_TYPE_DIRSTRUCT, id, sizeof(entry1.d.u), + LFS_TYPE_CTZSTRUCT, id, sizeof(entry1.d.u)), + &entry1.d.u})); + lfs1_entry_fromle32(&entry1.d); + if (err) { + goto cleanup; + } + } + + if (!lfs_pair_isnull(dir1.d.tail)) { + // find last block and update tail to thread into fs + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + while (dir2.split) { + err = lfs_dir_fetch(lfs, &dir2, dir2.tail); + if (err) { + goto cleanup; + } + } + + lfs_pair_tole32(dir2.pair); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_SOFTTAIL, 0x3ff, 8), dir1.d.tail})); + lfs_pair_fromle32(dir2.pair); + if (err) { + goto cleanup; + } + } + + // Copy over first block to thread into fs. Unfortunately + // if this fails there is not much we can do. + LFS_DEBUG("Migrating {0x%"PRIx32", 0x%"PRIx32"} " + "-> {0x%"PRIx32", 0x%"PRIx32"}", + lfs->root[0], lfs->root[1], dir1.head[0], dir1.head[1]); + + err = lfs_bd_erase(lfs, dir1.head[1]); + if (err) { + goto cleanup; + } + + err = lfs_dir_fetch(lfs, &dir2, lfs->root); + if (err) { + goto cleanup; + } + + for (lfs_off_t i = 0; i < dir2.off; i++) { + uint8_t dat; + err = lfs_bd_read(lfs, + NULL, &lfs->rcache, dir2.off, + dir2.pair[0], i, &dat, 1); + if (err) { + goto cleanup; + } + + err = lfs_bd_prog(lfs, + &lfs->pcache, &lfs->rcache, true, + dir1.head[1], i, &dat, 1); + if (err) { + goto cleanup; + } + } + + err = lfs_bd_flush(lfs, &lfs->pcache, &lfs->rcache, true); + if (err) { + goto cleanup; + } + } + + // Create new superblock. This marks a successful migration! + err = lfs1_dir_fetch(lfs, &dir1, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + dir2.pair[0] = dir1.pair[0]; + dir2.pair[1] = dir1.pair[1]; + dir2.rev = dir1.d.rev; + dir2.off = sizeof(dir2.rev); + dir2.etag = 0xffffffff; + dir2.count = 0; + dir2.tail[0] = lfs->lfs1->root[0]; + dir2.tail[1] = lfs->lfs1->root[1]; + dir2.erased = false; + dir2.split = true; + + lfs_superblock_t superblock = { + .version = LFS_DISK_VERSION, + .block_size = lfs->cfg->block_size, + .block_count = lfs->cfg->block_count, + .name_max = lfs->name_max, + .file_max = lfs->file_max, + .attr_max = lfs->attr_max, + }; + + lfs_superblock_tole32(&superblock); + err = lfs_dir_commit(lfs, &dir2, LFS_MKATTRS( + {LFS_MKTAG(LFS_TYPE_CREATE, 0, 0)}, + {LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, 8), "littlefs"}, + {LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, sizeof(superblock)), + &superblock})); + if (err) { + goto cleanup; + } + + // sanity check that fetch works + err = lfs_dir_fetch(lfs, &dir2, (const lfs_block_t[2]){0, 1}); + if (err) { + goto cleanup; + } + + // force compaction to prevent accidentally mounting v1 + dir2.erased = false; + err = lfs_dir_commit(lfs, &dir2, NULL, 0); + if (err) { + goto cleanup; + } + } + +cleanup: + lfs1_unmount(lfs); + LFS_TRACE("lfs_migrate -> %d", err); + return err; +} + +#endif diff --git a/lib/LITTLEFS/src/lfs.h b/lib/LITTLEFS/src/lfs.h new file mode 100644 index 00000000..35bbbabf --- /dev/null +++ b/lib/LITTLEFS/src/lfs.h @@ -0,0 +1,655 @@ +/* + * The little filesystem + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_H +#define LFS_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + + +/// Version info /// + +// Software library version +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_VERSION 0x00020002 +#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16)) +#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0)) + +// Version of On-disk data structures +// Major (top-nibble), incremented on backwards incompatible changes +// Minor (bottom-nibble), incremented on feature additions +#define LFS_DISK_VERSION 0x00020000 +#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16)) +#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0)) + + +/// Definitions /// + +// Type definitions +typedef uint32_t lfs_size_t; +typedef uint32_t lfs_off_t; + +typedef int32_t lfs_ssize_t; +typedef int32_t lfs_soff_t; + +typedef uint32_t lfs_block_t; + +// Maximum name size in bytes, may be redefined to reduce the size of the +// info struct. Limited to <= 1022. Stored in superblock and must be +// respected by other littlefs drivers. +#ifndef LFS_NAME_MAX +#define LFS_NAME_MAX 255 +#endif + +// Maximum size of a file in bytes, may be redefined to limit to support other +// drivers. Limited on disk to <= 4294967296. However, above 2147483647 the +// functions lfs_file_seek, lfs_file_size, and lfs_file_tell will return +// incorrect values due to using signed integers. Stored in superblock and +// must be respected by other littlefs drivers. +#ifndef LFS_FILE_MAX +#define LFS_FILE_MAX 2147483647 +#endif + +// Maximum size of custom attributes in bytes, may be redefined, but there is +// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. +#ifndef LFS_ATTR_MAX +#define LFS_ATTR_MAX 1022 +#endif + +// Possible error codes, these are negative to allow +// valid positive return values +enum lfs_error { + LFS_ERR_OK = 0, // No error + LFS_ERR_IO = -5, // Error during device operation + LFS_ERR_CORRUPT = -84, // Corrupted + LFS_ERR_NOENT = -2, // No directory entry + LFS_ERR_EXIST = -17, // Entry already exists + LFS_ERR_NOTDIR = -20, // Entry is not a dir + LFS_ERR_ISDIR = -21, // Entry is a dir + LFS_ERR_NOTEMPTY = -39, // Dir is not empty + LFS_ERR_BADF = -9, // Bad file number + LFS_ERR_FBIG = -27, // File too large + LFS_ERR_INVAL = -22, // Invalid parameter + LFS_ERR_NOSPC = -28, // No space left on device + LFS_ERR_NOMEM = -12, // No more memory available + LFS_ERR_NOATTR = -61, // No data/attr available + LFS_ERR_NAMETOOLONG = -36, // File name too long +}; + +// File types +enum lfs_type { + // file types + LFS_TYPE_REG = 0x001, + LFS_TYPE_DIR = 0x002, + + // internally used types + LFS_TYPE_SPLICE = 0x400, + LFS_TYPE_NAME = 0x000, + LFS_TYPE_STRUCT = 0x200, + LFS_TYPE_USERATTR = 0x300, + LFS_TYPE_FROM = 0x100, + LFS_TYPE_TAIL = 0x600, + LFS_TYPE_GLOBALS = 0x700, + LFS_TYPE_CRC = 0x500, + + // internally used type specializations + LFS_TYPE_CREATE = 0x401, + LFS_TYPE_DELETE = 0x4ff, + LFS_TYPE_SUPERBLOCK = 0x0ff, + LFS_TYPE_DIRSTRUCT = 0x200, + LFS_TYPE_CTZSTRUCT = 0x202, + LFS_TYPE_INLINESTRUCT = 0x201, + LFS_TYPE_SOFTTAIL = 0x600, + LFS_TYPE_HARDTAIL = 0x601, + LFS_TYPE_MOVESTATE = 0x7ff, + + // internal chip sources + LFS_FROM_NOOP = 0x000, + LFS_FROM_MOVE = 0x101, + LFS_FROM_USERATTRS = 0x102, +}; + +// File open flags +enum lfs_open_flags { + // open flags + LFS_O_RDONLY = 1, // Open a file as read only + LFS_O_WRONLY = 2, // Open a file as write only + LFS_O_RDWR = 3, // Open a file as read and write + LFS_O_CREAT = 0x0100, // Create a file if it does not exist + LFS_O_EXCL = 0x0200, // Fail if a file already exists + LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size + LFS_O_APPEND = 0x0800, // Move to end of file on every write + + // internally used flags + LFS_F_DIRTY = 0x010000, // File does not match storage + LFS_F_WRITING = 0x020000, // File has been written since last flush + LFS_F_READING = 0x040000, // File has been read since last flush + LFS_F_ERRED = 0x080000, // An error occured during write + LFS_F_INLINE = 0x100000, // Currently inlined in directory entry + LFS_F_OPENED = 0x200000, // File has been opened +}; + +// File seek flags +enum lfs_whence_flags { + LFS_SEEK_SET = 0, // Seek relative to an absolute position + LFS_SEEK_CUR = 1, // Seek relative to the current file position + LFS_SEEK_END = 2, // Seek relative to the end of the file +}; + + +// Configuration provided during initialization of the littlefs +struct lfs_config { + // Opaque user provided context that can be used to pass + // information to the block device operations + void *context; + + // Read a region in a block. Negative error codes are propogated + // to the user. + int (*read)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + + // Program a region in a block. The block must have previously + // been erased. Negative error codes are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*prog)(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + + // Erase a block. A block must be erased before being programmed. + // The state of an erased block is undefined. Negative error codes + // are propogated to the user. + // May return LFS_ERR_CORRUPT if the block should be considered bad. + int (*erase)(const struct lfs_config *c, lfs_block_t block); + + // Sync the state of the underlying block device. Negative error codes + // are propogated to the user. + int (*sync)(const struct lfs_config *c); + + // Minimum size of a block read. All read operations will be a + // multiple of this value. + lfs_size_t read_size; + + // Minimum size of a block program. All program operations will be a + // multiple of this value. + lfs_size_t prog_size; + + // Size of an erasable block. This does not impact ram consumption and + // may be larger than the physical erase size. However, non-inlined files + // take up at minimum one block. Must be a multiple of the read + // and program sizes. + lfs_size_t block_size; + + // Number of erasable blocks on the device. + lfs_size_t block_count; + + // Number of erase cycles before littlefs evicts metadata logs and moves + // the metadata to another block. Suggested values are in the + // range 100-1000, with large values having better performance at the cost + // of less consistent wear distribution. + // + // Set to -1 to disable block-level wear-leveling. + int32_t block_cycles; + + // Size of block caches. Each cache buffers a portion of a block in RAM. + // The littlefs needs a read cache, a program cache, and one additional + // cache per file. Larger caches can improve performance by storing more + // data and reducing the number of disk accesses. Must be a multiple of + // the read and program sizes, and a factor of the block size. + lfs_size_t cache_size; + + // Size of the lookahead buffer in bytes. A larger lookahead buffer + // increases the number of blocks found during an allocation pass. The + // lookahead buffer is stored as a compact bitmap, so each byte of RAM + // can track 8 blocks. Must be a multiple of 8. + lfs_size_t lookahead_size; + + // Optional statically allocated read buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *read_buffer; + + // Optional statically allocated program buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *prog_buffer; + + // Optional statically allocated lookahead buffer. Must be lookahead_size + // and aligned to a 32-bit boundary. By default lfs_malloc is used to + // allocate this buffer. + void *lookahead_buffer; + + // Optional upper limit on length of file names in bytes. No downside for + // larger names except the size of the info struct which is controlled by + // the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX when zero. Stored in + // superblock and must be respected by other littlefs drivers. + lfs_size_t name_max; + + // Optional upper limit on files in bytes. No downside for larger files + // but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX when zero. Stored + // in superblock and must be respected by other littlefs drivers. + lfs_size_t file_max; + + // Optional upper limit on custom attributes in bytes. No downside for + // larger attributes size but must be <= LFS_ATTR_MAX. Defaults to + // LFS_ATTR_MAX when zero. + lfs_size_t attr_max; +}; + +// File info structure +struct lfs_info { + // Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR + uint8_t type; + + // Size of the file, only valid for REG files. Limited to 32-bits. + lfs_size_t size; + + // Name of the file stored as a null-terminated string. Limited to + // LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to + // reduce RAM. LFS_NAME_MAX is stored in superblock and must be + // respected by other littlefs drivers. + char name[LFS_NAME_MAX+1]; +}; + +// Custom attribute structure, used to describe custom attributes +// committed atomically during file writes. +struct lfs_attr { + // 8-bit type of attribute, provided by user and used to + // identify the attribute + uint8_t type; + + // Pointer to buffer containing the attribute + void *buffer; + + // Size of attribute in bytes, limited to LFS_ATTR_MAX + lfs_size_t size; +}; + +// Optional configuration provided during lfs_file_opencfg +struct lfs_file_config { + // Optional statically allocated file buffer. Must be cache_size. + // By default lfs_malloc is used to allocate this buffer. + void *buffer; + + // Optional list of custom attributes related to the file. If the file + // is opened with read access, these attributes will be read from disk + // during the open call. If the file is opened with write access, the + // attributes will be written to disk every file sync or close. This + // write occurs atomically with update to the file's contents. + // + // Custom attributes are uniquely identified by an 8-bit type and limited + // to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller + // than the buffer, it will be padded with zeros. If the stored attribute + // is larger, then it will be silently truncated. If the attribute is not + // found, it will be created implicitly. + struct lfs_attr *attrs; + + // Number of custom attributes in the list + lfs_size_t attr_count; +}; + + +/// internal littlefs data structures /// +typedef struct lfs_cache { + lfs_block_t block; + lfs_off_t off; + lfs_size_t size; + uint8_t *buffer; +} lfs_cache_t; + +typedef struct lfs_mdir { + lfs_block_t pair[2]; + uint32_t rev; + lfs_off_t off; + uint32_t etag; + uint16_t count; + bool erased; + bool split; + lfs_block_t tail[2]; +} lfs_mdir_t; + +// littlefs directory type +typedef struct lfs_dir { + struct lfs_dir *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + lfs_off_t pos; + lfs_block_t head[2]; +} lfs_dir_t; + +// littlefs file type +typedef struct lfs_file { + struct lfs_file *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + + struct lfs_ctz { + lfs_block_t head; + lfs_size_t size; + } ctz; + + uint32_t flags; + lfs_off_t pos; + lfs_block_t block; + lfs_off_t off; + lfs_cache_t cache; + + const struct lfs_file_config *cfg; +} lfs_file_t; + +typedef struct lfs_superblock { + uint32_t version; + lfs_size_t block_size; + lfs_size_t block_count; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; +} lfs_superblock_t; + +typedef struct lfs_gstate { + uint32_t tag; + lfs_block_t pair[2]; +} lfs_gstate_t; + +// The littlefs filesystem type +typedef struct lfs { + lfs_cache_t rcache; + lfs_cache_t pcache; + + lfs_block_t root[2]; + struct lfs_mlist { + struct lfs_mlist *next; + uint16_t id; + uint8_t type; + lfs_mdir_t m; + } *mlist; + uint32_t seed; + + lfs_gstate_t gstate; + lfs_gstate_t gdisk; + lfs_gstate_t gdelta; + + struct lfs_free { + lfs_block_t off; + lfs_block_t size; + lfs_block_t i; + lfs_block_t ack; + uint32_t *buffer; + } free; + + const struct lfs_config *cfg; + lfs_size_t name_max; + lfs_size_t file_max; + lfs_size_t attr_max; + +#ifdef LFS_MIGRATE + struct lfs1 *lfs1; +#endif +} lfs_t; + + +/// Filesystem functions /// + +// Format a block device with the littlefs +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_format(lfs_t *lfs, const struct lfs_config *config); + +// Mounts a littlefs +// +// Requires a littlefs object and config struct. Multiple filesystems +// may be mounted simultaneously with multiple littlefs objects. Both +// lfs and config must be allocated while mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_mount(lfs_t *lfs, const struct lfs_config *config); + +// Unmounts a littlefs +// +// Does nothing besides releasing any allocated resources. +// Returns a negative error code on failure. +int lfs_unmount(lfs_t *lfs); + +/// General operations /// + +// Removes a file or directory +// +// If removing a directory, the directory must be empty. +// Returns a negative error code on failure. +int lfs_remove(lfs_t *lfs, const char *path); + +// Rename or move a file or directory +// +// If the destination exists, it must match the source in type. +// If the destination is a directory, the directory must be empty. +// +// Returns a negative error code on failure. +int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath); + +// Find info about a file or directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a negative error code on failure. +int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info); + +// Get a custom attribute +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than +// the buffer, it will be padded with zeros. If the stored attribute is larger, +// then it will be silently truncated. If no attribute is found, the error +// LFS_ERR_NOATTR is returned and the buffer is filled with zeros. +// +// Returns the size of the attribute, or a negative error code on failure. +// Note, the returned size is the size of the attribute on disk, irrespective +// of the size of the buffer. This can be used to dynamically allocate a buffer +// or check for existance. +lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path, + uint8_t type, void *buffer, lfs_size_t size); + +// Set custom attributes +// +// Custom attributes are uniquely identified by an 8-bit type and limited +// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be +// implicitly created. +// +// Returns a negative error code on failure. +int lfs_setattr(lfs_t *lfs, const char *path, + uint8_t type, const void *buffer, lfs_size_t size); + +// Removes a custom attribute +// +// If an attribute is not found, nothing happens. +// +// Returns a negative error code on failure. +int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type); + + +/// File operations /// + +// Open a file +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// Returns a negative error code on failure. +int lfs_file_open(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags); + +// Open a file with extra configuration +// +// The mode that the file is opened in is determined by the flags, which +// are values from the enum lfs_open_flags that are bitwise-ored together. +// +// The config struct provides additional config options per file as described +// above. The config struct must be allocated while the file is open, and the +// config struct must be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file, + const char *path, int flags, + const struct lfs_file_config *config); + +// Close a file +// +// Any pending writes are written out to storage as though +// sync had been called and releases any allocated resources. +// +// Returns a negative error code on failure. +int lfs_file_close(lfs_t *lfs, lfs_file_t *file); + +// Synchronize a file on storage +// +// Any pending writes are written out to storage. +// Returns a negative error code on failure. +int lfs_file_sync(lfs_t *lfs, lfs_file_t *file); + +// Read data from file +// +// Takes a buffer and size indicating where to store the read data. +// Returns the number of bytes read, or a negative error code on failure. +lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, + void *buffer, lfs_size_t size); + +// Write data to file +// +// Takes a buffer and size indicating the data to write. The file will not +// actually be updated on the storage until either sync or close is called. +// +// Returns the number of bytes written, or a negative error code on failure. +lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file, + const void *buffer, lfs_size_t size); + +// Change the position of the file +// +// The change in position is determined by the offset and whence flag. +// Returns the new position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file, + lfs_soff_t off, int whence); + +// Truncates the size of the file to the specified size +// +// Returns a negative error code on failure. +int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size); + +// Return the position of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR) +// Returns the position of the file, or a negative error code on failure. +lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file); + +// Change the position of the file to the beginning of the file +// +// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET) +// Returns a negative error code on failure. +int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file); + +// Return the size of the file +// +// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END) +// Returns the size of the file, or a negative error code on failure. +lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file); + + +/// Directory operations /// + +// Create a directory +// +// Returns a negative error code on failure. +int lfs_mkdir(lfs_t *lfs, const char *path); + +// Open a directory +// +// Once open a directory can be used with read to iterate over files. +// Returns a negative error code on failure. +int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path); + +// Close a directory +// +// Releases any allocated resources. +// Returns a negative error code on failure. +int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir); + +// Read an entry in the directory +// +// Fills out the info structure, based on the specified file or directory. +// Returns a positive value on success, 0 at the end of directory, +// or a negative error code on failure. +int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info); + +// Change the position of the directory +// +// The new off must be a value previous returned from tell and specifies +// an absolute offset in the directory seek. +// +// Returns a negative error code on failure. +int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off); + +// Return the position of the directory +// +// The returned offset is only meant to be consumed by seek and may not make +// sense, but does indicate the current position in the directory iteration. +// +// Returns the position of the directory, or a negative error code on failure. +lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir); + +// Change the position of the directory to the beginning of the directory +// +// Returns a negative error code on failure. +int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir); + + +/// Filesystem-level filesystem operations + +// Finds the current size of the filesystem +// +// Note: Result is best effort. If files share COW structures, the returned +// size may be larger than the filesystem actually is. +// +// Returns the number of allocated blocks, or a negative error code on failure. +lfs_ssize_t lfs_fs_size(lfs_t *lfs); + +// Traverse through all blocks in use by the filesystem +// +// The provided callback will be called with each block address that is +// currently in use by the filesystem. This can be used to determine which +// blocks are in use or how much of the storage is available. +// +// Returns a negative error code on failure. +int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data); + +#ifdef LFS_MIGRATE +// Attempts to migrate a previous version of littlefs +// +// Behaves similarly to the lfs_format function. Attempts to mount +// the previous version of littlefs and update the filesystem so it can be +// mounted with the current version of littlefs. +// +// Requires a littlefs object and config struct. This clobbers the littlefs +// object, and does not leave the filesystem mounted. The config struct must +// be zeroed for defaults and backwards compatibility. +// +// Returns a negative error code on failure. +int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg); +#endif + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif diff --git a/lib/LITTLEFS/src/lfs_util.c b/lib/LITTLEFS/src/lfs_util.c new file mode 100644 index 00000000..0b60e3b4 --- /dev/null +++ b/lib/LITTLEFS/src/lfs_util.c @@ -0,0 +1,33 @@ +/* + * lfs util functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#include "lfs_util.h" + +// Only compile if user does not provide custom config +#ifndef LFS_CONFIG + + +// Software CRC implementation with small lookup table +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { + static const uint32_t rtable[16] = { + 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, + 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, + 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, + 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, + }; + + const uint8_t *data = buffer; + + for (size_t i = 0; i < size; i++) { + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; + crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; + } + + return crc; +} + + +#endif diff --git a/lib/LITTLEFS/src/lfs_util.h b/lib/LITTLEFS/src/lfs_util.h new file mode 100644 index 00000000..dbb4c5ba --- /dev/null +++ b/lib/LITTLEFS/src/lfs_util.h @@ -0,0 +1,234 @@ +/* + * lfs utility functions + * + * Copyright (c) 2017, Arm Limited. All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + */ +#ifndef LFS_UTIL_H +#define LFS_UTIL_H + +// Users can override lfs_util.h with their own configuration by defining +// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h). +// +// If LFS_CONFIG is used, none of the default utils will be emitted and must be +// provided by the config file. To start, I would suggest copying lfs_util.h +// and modifying as needed. +#ifdef LFS_CONFIG +#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x) +#define LFS_STRINGIZE2(x) #x +#include LFS_STRINGIZE(LFS_CONFIG) +#else + +// System includes +#include +#include +#include +#include + +#ifndef LFS_NO_MALLOC +#include +#endif +#ifndef LFS_NO_ASSERT +#include +#endif +#if !defined(LFS_NO_DEBUG) || \ + !defined(LFS_NO_WARN) || \ + !defined(LFS_NO_ERROR) || \ + defined(LFS_YES_TRACE) +#include +#endif + +#ifdef __cplusplus +extern "C" +{ +#endif + + +// Macros, may be replaced by system specific wrappers. Arguments to these +// macros must not have side-effects as the macros can be removed for a smaller +// code footprint + +// Logging functions +#ifdef LFS_YES_TRACE +#define LFS_TRACE_(fmt, ...) \ + printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") +#else +#define LFS_TRACE(...) +#endif + +#ifndef LFS_NO_DEBUG +#define LFS_DEBUG_(fmt, ...) \ + printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") +#else +#define LFS_DEBUG(...) +#endif + +#ifndef LFS_NO_WARN +#define LFS_WARN_(fmt, ...) \ + printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") +#else +#define LFS_WARN(...) +#endif + +#ifndef LFS_NO_ERROR +#define LFS_ERROR_(fmt, ...) \ + printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) +#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") +#else +#define LFS_ERROR(...) +#endif + +// Runtime assertions +#ifndef LFS_NO_ASSERT +#define LFS_ASSERT(test) assert(test) +#else +#define LFS_ASSERT(test) +#endif + + +// Builtin functions, these may be replaced by more efficient +// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more +// expensive basic C implementation for debugging purposes + +// Min/max functions for unsigned 32-bit numbers +static inline uint32_t lfs_max(uint32_t a, uint32_t b) { + return (a > b) ? a : b; +} + +static inline uint32_t lfs_min(uint32_t a, uint32_t b) { + return (a < b) ? a : b; +} + +// Align to nearest multiple of a size +static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { + return a - (a % alignment); +} + +static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { + return lfs_aligndown(a + alignment-1, alignment); +} + +// Find the smallest power of 2 greater than or equal to a +static inline uint32_t lfs_npw2(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return 32 - __builtin_clz(a-1); +#else + uint32_t r = 0; + uint32_t s; + a -= 1; + s = (a > 0xffff) << 4; a >>= s; r |= s; + s = (a > 0xff ) << 3; a >>= s; r |= s; + s = (a > 0xf ) << 2; a >>= s; r |= s; + s = (a > 0x3 ) << 1; a >>= s; r |= s; + return (r | (a >> 1)) + 1; +#endif +} + +// Count the number of trailing binary zeros in a +// lfs_ctz(0) may be undefined +static inline uint32_t lfs_ctz(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__) + return __builtin_ctz(a); +#else + return lfs_npw2((a & -a) + 1) - 1; +#endif +} + +// Count the number of binary ones in a +static inline uint32_t lfs_popc(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_popcount(a); +#else + a = a - ((a >> 1) & 0x55555555); + a = (a & 0x33333333) + ((a >> 2) & 0x33333333); + return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24; +#endif +} + +// Find the sequence comparison of a and b, this is the distance +// between a and b ignoring overflow +static inline int lfs_scmp(uint32_t a, uint32_t b) { + return (int)(unsigned)(a - b); +} + +// Convert between 32-bit little-endian and native order +static inline uint32_t lfs_fromle32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return a; +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return __builtin_bswap32(a); +#else + return (((uint8_t*)&a)[0] << 0) | + (((uint8_t*)&a)[1] << 8) | + (((uint8_t*)&a)[2] << 16) | + (((uint8_t*)&a)[3] << 24); +#endif +} + +static inline uint32_t lfs_tole32(uint32_t a) { + return lfs_fromle32(a); +} + +// Convert between 32-bit big-endian and native order +static inline uint32_t lfs_frombe32(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)) + return __builtin_bswap32(a); +#elif !defined(LFS_NO_INTRINSICS) && ( \ + (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \ + (defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)) + return a; +#else + return (((uint8_t*)&a)[0] << 24) | + (((uint8_t*)&a)[1] << 16) | + (((uint8_t*)&a)[2] << 8) | + (((uint8_t*)&a)[3] << 0); +#endif +} + +static inline uint32_t lfs_tobe32(uint32_t a) { + return lfs_frombe32(a); +} + +// Calculate CRC-32 with polynomial = 0x04c11db7 +uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); + +// Allocate memory, only used if buffers are not provided to littlefs +// Note, memory must be 64-bit aligned +static inline void *lfs_malloc(size_t size) { +#ifndef LFS_NO_MALLOC + return malloc(size); +#else + (void)size; + return NULL; +#endif +} + +// Deallocate memory, only used if buffers are not provided to littlefs +static inline void lfs_free(void *p) { +#ifndef LFS_NO_MALLOC + free(p); +#else + (void)p; +#endif +} + + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif +#endif diff --git a/lib/LITTLEFS/src/littlefs_api.c b/lib/LITTLEFS/src/littlefs_api.c new file mode 100644 index 00000000..4098487c --- /dev/null +++ b/lib/LITTLEFS/src/littlefs_api.c @@ -0,0 +1,58 @@ +/** + * @file littlefs_api.c + * @brief Maps the HAL of esp_partition <-> littlefs + * @author Brian Pugh + */ + +#define ESP_LOCAL_LOG_LEVEL ESP_LOG_INFO + +#include "esp_log.h" +#include "esp_partition.h" +#include "esp_vfs.h" +#include "lfs.h" +#include "esp_littlefs.h" +#include "littlefs_api.h" + +static const char TAG[] = "esp_littlefs_api"; + +int littlefs_api_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size) { + esp_littlefs_t * efs = c->context; + size_t part_off = (block * c->block_size) + off; + esp_err_t err = esp_partition_read(efs->partition, part_off, buffer, size); + if (err) { + ESP_LOGE(TAG, "failed to read addr %08x, size %08x, err %d", part_off, size, err); + return LFS_ERR_IO; + } + return 0; +} + +int littlefs_api_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size) { + esp_littlefs_t * efs = c->context; + size_t part_off = (block * c->block_size) + off; + esp_err_t err = esp_partition_write(efs->partition, part_off, buffer, size); + if (err) { + ESP_LOGE(TAG, "failed to write addr %08x, size %08x, err %d", part_off, size, err); + return LFS_ERR_IO; + } + return 0; +} + +int littlefs_api_erase(const struct lfs_config *c, lfs_block_t block) { + esp_littlefs_t * efs = c->context; + size_t part_off = block * c->block_size; + esp_err_t err = esp_partition_erase_range(efs->partition, part_off, c->block_size); + if (err) { + ESP_LOGE(TAG, "failed to erase addr %08x, size %08x, err %d", part_off, c->block_size, err); + return LFS_ERR_IO; + } + return 0; + +} + +int littlefs_api_sync(const struct lfs_config *c) { + /* Unnecessary for esp-idf */ + return 0; +} + diff --git a/lib/LITTLEFS/src/littlefs_api.h b/lib/LITTLEFS/src/littlefs_api.h new file mode 100644 index 00000000..c1b2adc9 --- /dev/null +++ b/lib/LITTLEFS/src/littlefs_api.h @@ -0,0 +1,106 @@ +#ifndef ESP_LITTLEFS_API_H__ +#define ESP_LITTLEFS_API_H__ + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_vfs.h" +#include "esp_partition.h" +#include "lfs.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief a file descriptor + * That's also a singly linked list used for keeping tracks of all opened file descriptor + * + * Shortcomings/potential issues of 32-bit hash (when CONFIG_LITTLEFS_USE_ONLY_HASH) listed here: + * * unlink - If a different file is open that generates a hash collision, it will report an + * error that it cannot unlink an open file. + * * rename - If a different file is open that generates a hash collision with + * src or dst, it will report an error that it cannot rename an open file. + * Potential consequences: + * 1. A file cannot be deleted while a collision-geneating file is open. + * Worst-case, if the other file is always open during the lifecycle + * of your app, it's collision file cannot be deleted, which in the + * worst-case could cause storage-capacity issues. + * 2. Same as (1), but for renames + */ +typedef struct _vfs_littlefs_file_t { + lfs_file_t file; + uint32_t hash; + struct _vfs_littlefs_file_t * next; /*!< Pointer to next file in Singly Linked List */ +#ifndef CONFIG_LITTLEFS_USE_ONLY_HASH + char * path; +#endif +} vfs_littlefs_file_t; + +/** + * @brief littlefs definition structure + */ +typedef struct { + lfs_t *fs; /*!< Handle to the underlying littlefs */ + SemaphoreHandle_t lock; /*!< FS lock */ + const esp_partition_t* partition; /*!< The partition on which littlefs is located */ + char base_path[ESP_VFS_PATH_MAX+1]; /*!< Mount point */ + + struct lfs_config cfg; /*!< littlefs Mount configuration */ + + vfs_littlefs_file_t *file; /*!< Singly Linked List of files */ + + vfs_littlefs_file_t **cache; /*!< A cache of pointers to the opened files */ + uint16_t cache_size; /*!< The cache allocated size (in pointers) */ + uint16_t fd_count; /*!< The count of opened file descriptor used to speed up computation */ +} esp_littlefs_t; + +/** + * @brief Read a region in a block. + * + * Negative error codes are propogated to the user. + * + * @return errorcode. 0 on success. + */ +int littlefs_api_read(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, void *buffer, lfs_size_t size); + +/** + * @brief Program a region in a block. + * + * The block must have previously been erased. + * Negative error codes are propogated to the user. + * May return LFS_ERR_CORRUPT if the block should be considered bad. + * + * @return errorcode. 0 on success. + */ +int littlefs_api_prog(const struct lfs_config *c, lfs_block_t block, + lfs_off_t off, const void *buffer, lfs_size_t size); + +/** + * @brief Erase a block. + * + * A block must be erased before being programmed. + * The state of an erased block is undefined. + * Negative error codes are propogated to the user. + * May return LFS_ERR_CORRUPT if the block should be considered bad. + * @return errorcode. 0 on success. + */ +int littlefs_api_erase(const struct lfs_config *c, lfs_block_t block); + +/** + * @brief Sync the state of the underlying block device. + * + * Negative error codes are propogated to the user. + * + * @return errorcode. 0 on success. + */ +int littlefs_api_sync(const struct lfs_config *c); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/platformio.ini b/platformio.ini index 84b8d84b..37a44c82 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,8 @@ default_envs = esp8266 framework = arduino [env:esp32] -platform = espressif32 +platform = https://github.com/platformio/platform-espressif32.git +build_flags = ${env.build_flags} -D=${PIOENV} board = esp32dev board_build.partitions = partitions_custom.csv monitor_filters = esp32_exception_decoder @@ -23,7 +24,6 @@ monitor_speed = 115200 lib_deps = ModuleInterface@3.5.1 ArduinoJson@5.* - AsyncTCP ESP32Servo Bounce2 PubSubClient @@ -34,8 +34,8 @@ lib_deps = Adafruit BME280 Library [env:esp8266] -build_flags =-Wno-deprecated -platform = espressif8266 +platform = https://github.com/platformio/platform-espressif8266.git +build_flags = ${env.build_flags} -D=${PIOENV} board = nodemcuv2 monitor_filters = esp8266_exception_decoder monitor_speed = 115200 diff --git a/src/Cmd.cpp b/src/Cmd.cpp index 23198cb2..93ec23cc 100644 --- a/src/Cmd.cpp +++ b/src/Cmd.cpp @@ -19,17 +19,17 @@ void CMD_init() { sCmd.addCommand("switch", switch_); -#ifdef analog_enable +#ifdef ANALOG_ENABLED sCmd.addCommand("analog", analog); #endif -#ifdef level_enable +#ifdef LEVEL_ENABLED sCmd.addCommand("levelPr", levelPr); sCmd.addCommand("ultrasonicCm", ultrasonicCm); #endif -#ifdef dallas_enable +#ifdef DALLAS_ENABLED sCmd.addCommand("dallas", dallas); #endif -#ifdef dht_enable +#ifdef DHT_ENABLED sCmd.addCommand("dhtT", dhtT); sCmd.addCommand("dhtH", dhtH); sCmd.addCommand("dhtPerception", dhtP); @@ -37,34 +37,34 @@ void CMD_init() { sCmd.addCommand("dhtDewpoint", dhtD); #endif -#ifdef bmp_enable +#ifdef BMP_ENABLED sCmd.addCommand("bmp280T", bmp280T); sCmd.addCommand("bmp280P", bmp280P); #endif -#ifdef bme_enable +#ifdef BME_ENABLED sCmd.addCommand("bme280T", bme280T); sCmd.addCommand("bme280P", bme280P); sCmd.addCommand("bme280H", bme280H); sCmd.addCommand("bme280A", bme280A); #endif -#ifdef stepper_enable +#ifdef STEPPER_ENABLED sCmd.addCommand("stepper", stepper); sCmd.addCommand("stepperSet", stepperSet); #endif -#ifdef servo_enable +#ifdef SERVO_ENABLED sCmd.addCommand("servo", servo_); sCmd.addCommand("servoSet", servoSet); #endif -#ifdef serial_enable +#ifdef SERIAL_ENABLED sCmd.addCommand("serialBegin", serialBegin); sCmd.addCommand("serialWrite", serialWrite); #endif -#ifdef logging_enable +#ifdef LOGGING_ENABLED sCmd.addCommand("logging", logging); #endif @@ -83,7 +83,7 @@ void CMD_init() { sCmd.addCommand("mqtt", mqttOrderSend); sCmd.addCommand("http", httpOrderSend); -#ifdef push_enable +#ifdef PUSH_ENABLED sCmd.addCommand("push", pushControl); #endif @@ -345,7 +345,7 @@ void textSet() { } //===================================================================================================================================== //=========================================Модуль шагового мотора====================================================================== -#ifdef stepper_enable +#ifdef STEPPER_ENABLED //stepper 1 12 13 void stepper() { String stepper_number = sCmd.next(); @@ -407,7 +407,7 @@ void stepperSet() { #endif //==================================================================================================================================================== //=================================================================Сервоприводы======================================================================= -#ifdef servo_enable +#ifdef SERVO_ENABLED //servo 1 13 50 Мой#сервопривод Сервоприводы 0 100 0 180 2 void servo_() { String servo_number = sCmd.next(); @@ -501,7 +501,7 @@ void servoSet() { #endif //==================================================================================================================================================== //===================================================================================serial=========================================================== -#ifdef serial_enable +#ifdef SERIAL_ENABLED void serialBegin() { //String s_speed = sCmd.next(); //String rxPin = sCmd.next(); @@ -545,7 +545,7 @@ void firmwareVersion() { String widget_name = sCmd.next(); String page_name = sCmd.next(); String page_number = sCmd.next(); - jsonWriteStr(configLiveJson, "firmver", firmware_version); + jsonWriteStr(configLiveJson, "firmver", FIRMWARE_VERSION); choose_widget_and_create(widget_name, page_name, page_number, "any-data", "firmver"); } diff --git a/src/Init.cpp b/src/Init.cpp index 7e785d69..051d594d 100644 --- a/src/Init.cpp +++ b/src/Init.cpp @@ -7,18 +7,15 @@ UptimeInterval myUptime(10); void handle_uptime(); void handle_statistics(); -void File_system_init() { - if (!LittleFS.begin()) { - Serial.println("[E] LittleFS"); - return; +void loadConfig() { + if (fileSystemInit()) { + configSetupJson = readFile("config.json", 4096); + configSetupJson.replace(" ", ""); + configSetupJson.replace("\r\n", ""); } - configSetupJson = readFile("config.json", 4096); - configSetupJson.replace(" ", ""); - configSetupJson.replace("\r\n", ""); - jsonWriteStr(configSetupJson, "chipID", chipId); - jsonWriteStr(configSetupJson, "firmware_version", firmware_version); + jsonWriteStr(configSetupJson, "firmware_version", FIRMWARE_VERSION); prex = jsonReadStr(configSetupJson, "mqttPrefix") + "/" + chipId; @@ -62,10 +59,10 @@ void Device_init() { ts.remove(i); } -#ifdef layout_in_ram +#ifdef LAYOUT_IN_RAM all_widgets = ""; #else - LittleFS.remove("/layout.txt"); + removeFile("/layout.txt"); #endif txtExecution("firmware.c.txt"); @@ -85,11 +82,14 @@ void uptime_init() { handle_uptime(); }, nullptr, true); - ts.add( - STATISTICS, statistics_update, [&](void*) { - handle_statistics(); - }, - nullptr, true); + + if (TELEMETRY_UPDATE_INTERVAL) { + ts.add( + STATISTICS, TELEMETRY_UPDATE_INTERVAL, [&](void*) { + handle_statistics(); + }, + nullptr, true); + } } void handle_uptime() { @@ -123,7 +123,7 @@ void handle_statistics() { urls += "&"; //----------------------------------------------------------------- urls += "ver: "; - urls += String(firmware_version); + urls += String(FIRMWARE_VERSION); //----------------------------------------------------------------- String stat = getURL(urls); //Serial.println(stat); diff --git a/src/Logging.cpp b/src/Logging.cpp index 6ed5c30d..4f0505ab 100644 --- a/src/Logging.cpp +++ b/src/Logging.cpp @@ -1,8 +1,11 @@ #include "Global.h" +// +#include + void sendLogData(String file, String topic); -#ifdef logging_enable +#ifdef LOGGING_ENABLED //===============================================Логирование============================================================ //logging temp1 1 10 Температура Датчики 2 void logging() { @@ -22,7 +25,7 @@ void logging() { LOG1, period_min.toInt() * 1000 * 60, [&](void*) { String tmp_buf_1 = selectFromMarkerToMarker(logging_value_names_list, ",", 0); deleteOldDate("log." + tmp_buf_1 + ".txt", jsonReadInt(configOptionJson, tmp_buf_1 + "_c"), jsonReadStr(configLiveJson, tmp_buf_1)); - Serial.println("[I] LOGGING for sensor '" + tmp_buf_1 + "' done"); + Serial.println("[I] LOGGING for sensor '" + tmp_buf_1 + "' done"); }, nullptr, false); } @@ -31,7 +34,7 @@ void logging() { LOG2, period_min.toInt() * 1000 * 60, [&](void*) { String tmp_buf_2 = selectFromMarkerToMarker(logging_value_names_list, ",", 1); deleteOldDate("log." + tmp_buf_2 + ".txt", jsonReadInt(configOptionJson, tmp_buf_2 + "_c"), jsonReadStr(configLiveJson, tmp_buf_2)); - Serial.println("[I] LOGGING for sensor '" + tmp_buf_2 + "' done"); + Serial.println("[I] LOGGING for sensor '" + tmp_buf_2 + "' done"); }, nullptr, false); } @@ -40,7 +43,7 @@ void logging() { LOG3, period_min.toInt() * 1000 * 60, [&](void*) { String tmp_buf_3 = selectFromMarkerToMarker(logging_value_names_list, ",", 2); deleteOldDate("log." + tmp_buf_3 + ".txt", jsonReadInt(configOptionJson, tmp_buf_3 + "_c"), jsonReadStr(configLiveJson, tmp_buf_3)); - Serial.println("[I] LOGGING for sensor '" + tmp_buf_3 + "' done"); + Serial.println("[I] LOGGING for sensor '" + tmp_buf_3 + "' done"); }, nullptr, false); } @@ -49,7 +52,7 @@ void logging() { LOG4, period_min.toInt() * 1000 * 60, [&](void*) { String tmp_buf_4 = selectFromMarkerToMarker(logging_value_names_list, ",", 3); deleteOldDate("log." + tmp_buf_4 + ".txt", jsonReadInt(configOptionJson, tmp_buf_4 + "_c"), jsonReadStr(configLiveJson, tmp_buf_4)); - Serial.println("[I] LOGGING for sensor '" + tmp_buf_4 + "' done"); + Serial.println("[I] LOGGING for sensor '" + tmp_buf_4 + "' done"); }, nullptr, false); } @@ -58,35 +61,33 @@ void logging() { LOG5, period_min.toInt() * 1000 * 60, [&](void*) { String tmp_buf_5 = selectFromMarkerToMarker(logging_value_names_list, ",", 4); deleteOldDate("log." + tmp_buf_5 + ".txt", jsonReadInt(configOptionJson, tmp_buf_5 + "_c"), jsonReadStr(configLiveJson, tmp_buf_5)); - Serial.println("[I] LOGGING for sensor '" + tmp_buf_5 + "' done"); + Serial.println("[I] LOGGING for sensor '" + tmp_buf_5 + "' done"); }, nullptr, false); } } //=========================================Удаление стрых данных и запись новых================================================================== -void deleteOldDate(String file, int seted_number_of_lines, String date_to_add) { - String log_date = readFile(file, 5000); - int current_number_of_lines = itemsCount(log_date, "\r\n"); - Serial.println("=====> [i] in log file " + file + " " + current_number_of_lines + " lines"); +void deleteOldDate(const String filename, int max_lines_cnt, String date_to_add) { + String log_date = readFile(filename, 5120); + size_t lines_cnt = itemsCount(log_date, "\r\n"); - if (current_number_of_lines > seted_number_of_lines + 1) { - LittleFS.remove("/" + file); - current_number_of_lines = 0; + Serial.printf("[I] log %s of %d lines\n", filename.c_str(), lines_cnt); + + if ((lines_cnt > max_lines_cnt + 1) || !lines_cnt) { + removeFile("/" + filename); + lines_cnt = 0; } - if (current_number_of_lines == 0) { - LittleFS.remove("/" + file); - current_number_of_lines = 0; - } - if (current_number_of_lines > seted_number_of_lines) { + + if (lines_cnt > max_lines_cnt) { log_date = deleteBeforeDelimiter(log_date, "\r\n"); if (getTimeUnix() != "failed") { log_date += getTimeUnix() + " " + date_to_add + "\r\n"; - writeFile(file, log_date); + writeFile(filename, log_date); } } else { if (getTimeUnix() != "failed") { - addFile(file, getTimeUnix() + " " + date_to_add); + addFile(filename, getTimeUnix() + " " + date_to_add); } } log_date = ""; @@ -133,7 +134,7 @@ void sendLogData(String file, String topic) { Serial.println(json_array); sendCHART(topic, json_array); json_array = ""; - getMemoryLoad("[I] after send log date"); + printMemoryStatus("[I] send log date"); } } @@ -154,12 +155,13 @@ void sendLogData(String file, String topic) { } getMemoryLoad("[I] after send log date"); */ + //=========================================Очистка данных=================================================================================== void clean_log_date() { String all_line = logging_value_names_list; while (all_line.length() != 0) { String tmp = selectToMarker(all_line, ","); - LittleFS.remove("/log." + tmp + ".txt"); + removeFile("/log." + tmp + ".txt"); all_line = deleteBeforeDelimiter(all_line, ","); } all_line = ""; diff --git a/src/Mqtt.cpp b/src/Mqtt.cpp index 46fbd817..0af3e13a 100644 --- a/src/Mqtt.cpp +++ b/src/Mqtt.cpp @@ -1,5 +1,8 @@ #include "Global.h" +// +#include + // Errors int wifi_lost_error = 0; int mqtt_lost_error = 0; @@ -14,7 +17,7 @@ void outcoming_date(); //===============================================ИНИЦИАЛИЗАЦИЯ================================================ void MQTT_init() { ts.add( - WIFI_MQTT_CONNECTION_CHECK, wifi_mqtt_reconnecting, [&](void*) { + WIFI_MQTT_CONNECTION_CHECK, MQTT_RECONNECT_INTERVAL, [&](void*) { if (WiFi.status() == WL_CONNECTED) { Serial.println("[VV] WiFi-ok"); if (client_mqtt.connected()) { @@ -73,7 +76,7 @@ boolean MQTT_Connecting() { Serial.println("[V] Callback set, subscribe done"); res = true; } else { - Serial.println("[E] try again in " + String(wifi_mqtt_reconnecting / 1000) + " sec"); + Serial.println("[E] try again in " + String(MQTT_RECONNECT_INTERVAL / 1000) + " sec"); led_blink("fast"); } } @@ -133,7 +136,7 @@ void outcoming_date() { sendAllWigets(); sendAllData(); -#ifdef logging_enable +#ifdef LOGGING_ENABLED choose_log_date_and_send(); #endif @@ -179,7 +182,7 @@ void sendCONTROL(String id, String topik, String state) { //=====================================================ОТПРАВЛЯЕМ ВИДЖЕТЫ======================================================== -#ifdef layout_in_ram +#ifdef LAYOUT_IN_RAM void sendAllWigets() { if (all_widgets != "") { int counter = 0; @@ -203,26 +206,26 @@ void sendAllWigets() { } #endif -#ifndef layout_in_ram +#ifndef LAYOUT_IN_RAM void sendAllWigets() { - auto file = LittleFS.open("/layout.txt", "r"); + auto file = seekFile("/layout.txt"); if (!file) { - Serial.println("[e] on open layout.txt"); return; } - file.seek(0, SeekSet); //поставим курсор в начало файла while (file.position() != file.size()) { - String widget_to_send = file.readStringUntil('\n'); - Serial.println("[V] " + widget_to_send); - sendMQTT("config", widget_to_send); + String payload = file.readStringUntil('\n'); + Serial.println("[V] " + payload); + sendMQTT("config", payload); } + file.close(); } #endif + //=====================================================ОТПРАВЛЯЕМ ДАННЫЕ В ВИДЖЕТЫ ПРИ ОБНОВЛЕНИИ СТРАНИЦЫ======================================================== void sendAllData() { //берет строку json и ключи превращает в топики а значения колючей в них посылает String current_config = configLiveJson; //{"name":"MODULES","lang":"","ip":"192.168.43.60","DS":"34.00","rel1":"1","rel2":"1"} - getMemoryLoad("[I] after send all date"); + printMemoryStatus("[I] after send all date"); current_config.replace("{", ""); current_config.replace("}", ""); //"name":"MODULES","lang":"","ip":"192.168.43.60","DS":"34.00","rel1":"1","rel2":"1" current_config += ","; //"name":"MODULES","lang":"","ip":"192.168.43.60","DS":"34.00","rel1":"1","rel2":"1", diff --git a/src/Sensors.cpp b/src/Sensors.cpp index 24ac9f0a..74422070 100644 --- a/src/Sensors.cpp +++ b/src/Sensors.cpp @@ -23,7 +23,7 @@ void sensors_init() { static int counter; counter++; -#ifdef level_enable +#ifdef LEVEL_ENABLED if (sensors_reading_map[0] == 1) ultrasonic_reading(); #endif @@ -31,19 +31,19 @@ void sensors_init() { if (counter > 10) { counter = 0; -#ifdef analog_enable +#ifdef ANALOG_ENABLED if (sensors_reading_map[1] == 1) analog_reading1(); if (sensors_reading_map[2] == 1) analog_reading2(); #endif -#ifdef dallas_enable +#ifdef DALLAS_ENABLED if (sensors_reading_map[3] == 1) dallas_reading(); #endif -#ifdef dht_enable +#ifdef DHT_ENABLED if (sensors_reading_map[4] == 1) dhtT_reading(); if (sensors_reading_map[5] == 1) @@ -56,14 +56,14 @@ void sensors_init() { dhtD_reading(); #endif -#ifdef bmp_enable +#ifdef BMP_ENABLED if (sensors_reading_map[9] == 1) bmp280T_reading(); if (sensors_reading_map[10] == 1) bmp280P_reading(); #endif -#ifdef bme_enable +#ifdef BME_ENABLED if (sensors_reading_map[11] == 1) bme280T_reading(); if (sensors_reading_map[12] == 1) @@ -80,7 +80,7 @@ void sensors_init() { //========================================================================================================================================= //=========================================Модуль измерения уровня в баке================================================================== -#ifdef level_enable +#ifdef LEVEL_ENABLED //levelPr p 14 12 Вода#в#баке,#% Датчики fill-gauge 125 20 1 void levelPr() { String value_name = sCmd.next(); @@ -138,7 +138,7 @@ void ultrasonic_reading() { distance_cm = duration_ / 29 / 2; distance_cm = medianFilter.filtered(distance_cm); //отсечение промахов медианным фильтром counter++; - if (counter > tank_level_times_to_send) { + if (counter > TANK_LEVEL_SAMPLES) { counter = 0; level = map(distance_cm, jsonReadInt(configOptionJson, "e_lev"), @@ -158,7 +158,7 @@ void ultrasonic_reading() { #endif //========================================================================================================================================= //=========================================Модуль аналогового сенсора====================================================================== -#ifdef analog_enable +#ifdef ANALOG_ENABLED //analog adc 0 Аналоговый#вход,#% Датчики any-data 1 1023 1 100 1 void analog() { String value_name = sCmd.next(); @@ -226,7 +226,7 @@ void analog_reading2() { #endif //========================================================================================================================================= //=========================================Модуль температурного сенсора ds18b20=========================================================== -#ifdef dallas_enable +#ifdef DALLAS_ENABLED void dallas() { //String value_name = sCmd.next(); String pin = sCmd.next(); @@ -255,7 +255,7 @@ void dallas_reading() { #endif //========================================================================================================================================= //=========================================Модуль сенсоров DHT============================================================================= -#ifdef dht_enable +#ifdef DHT_ENABLED //dhtT t 2 dht11 Температура#DHT,#t°C Датчики any-data 1 void dhtT() { String value_name = sCmd.next(); diff --git a/src/Upgrade.cpp b/src/Upgrade.cpp index d050f0d5..5cc21f2d 100644 --- a/src/Upgrade.cpp +++ b/src/Upgrade.cpp @@ -68,10 +68,10 @@ void upgrade_firmware() { Serial.println("Restart..."); ESP.restart(); } else { - Serial.println("[e] on build"); + Serial.println("[E] on build"); } } else { - Serial.println("[e] on upgrade"); + Serial.println("[E] on upgrade"); } } diff --git a/src/Utils/FileUtils.cpp b/src/Utils/FileUtils.cpp index bb0aa09a..6778f7c0 100644 --- a/src/Utils/FileUtils.cpp +++ b/src/Utils/FileUtils.cpp @@ -1,6 +1,29 @@ #include "Utils/FileUtils.h" -#include + +bool fileSystemInit() { + if (!LittleFS.begin()) { + Serial.println("[E] LittleFS"); + return false; + } + return true; +} + +void removeFile(const String filename) { + if (!LittleFS.remove(filename)) { + Serial.printf("[E] on remove %s", filename.c_str()); + } +} + +File seekFile(const String filename, size_t position) { + auto file = LittleFS.open(filename, "r"); + if (!file) { + Serial.printf("[E] on open %s", filename.c_str()); + } + // поставим курсор в начало файла + file.seek(position, SeekSet); + return file; +} String readFileString(const String filename, const String to_find) { String res = "Failed"; diff --git a/src/Utils/TimeUtils.cpp b/src/Utils/TimeUtils.cpp index 5c785384..fbbcb00c 100644 --- a/src/Utils/TimeUtils.cpp +++ b/src/Utils/TimeUtils.cpp @@ -22,9 +22,9 @@ void reconfigTime() { if (WiFi.status() == WL_CONNECTED) { String ntp = jsonReadStr(configSetupJson, "ntp"); configTime(0, 0, ntp.c_str()); - int i = 0; - Serial.println("[I] Start time sync"); + Serial.println("[I] Time sync"); #ifdef ESP32 + uint8_t i = 0; struct tm timeinfo; while (!getLocalTime(&timeinfo) && i <= 4) { Serial.print("."); @@ -33,6 +33,7 @@ void reconfigTime() { } #endif #ifdef ESP8266 + //uint8_t i = 0; //while (!time(nullptr) && i < 4) { // Serial.print("."); // i++; diff --git a/src/Web.cpp b/src/Web.cpp index 44792d4e..3f2e412a 100644 --- a/src/Web.cpp +++ b/src/Web.cpp @@ -124,7 +124,7 @@ void web_init() { request->send(200, "text/text", "OK"); } //-------------------------------------------------------------------------------- -#ifdef logging_enable +#ifdef LOGGING_ENABLED if (request->hasArg("cleanlog")) { clean_log_date(); request->send(200, "text/text", "OK"); @@ -147,7 +147,7 @@ void web_init() { } //-------------------------------------------------------------------------------- if (request->hasArg("updatelist")) { - LittleFS.remove("/dev.csv"); + removeFile("/dev.csv"); addFile("dev.csv", "device id;device name;ip address"); request->redirect("/?set.udp"); } @@ -270,7 +270,7 @@ void web_init() { request->send(200, "text/text", tmp); } //==============================push settings============================================= -#ifdef push_enable +#ifdef PUSH_ENABLED if (request->hasArg("pushingboxid")) { jsonWriteStr(configSetupJson, "pushingboxid", request->getParam("pushingboxid")->value()); saveConfig(); @@ -292,10 +292,10 @@ void web_init() { int case_of_update; if (WiFi.status() != WL_CONNECTED) last_version = "nowifi"; - if (!mb_4_of_memory) last_version = "less"; + if (!FLASH_4MB) last_version = "less"; - if (last_version == firmware_version) case_of_update = 1; - if (last_version != firmware_version) case_of_update = 2; + if (last_version == FIRMWARE_VERSION) case_of_update = 1; + if (last_version != FIRMWARE_VERSION) case_of_update = 2; if (last_version == "error") case_of_update = 3; if (last_version == "") case_of_update = 4; if (last_version == "less") case_of_update = 5; diff --git a/src/Web_server.cpp b/src/Web_server.cpp index 6bd53cca..cd0c3d02 100644 --- a/src/Web_server.cpp +++ b/src/Web_server.cpp @@ -1,10 +1,12 @@ #include "Global.h" +#include "Utils/FileUtils.h" + void Web_server_init() { /********************************************************************************* ***************************************OTA**************************************** *********************************************************************************/ -#ifdef OTA_enable +#ifdef OTA_UPDATES_ENABLED ArduinoOTA.onStart([]() { events.send("Update Start", "ota"); }); @@ -34,7 +36,7 @@ void Web_server_init() { /********************************************************************************* **************************************MDNS**************************************** *********************************************************************************/ -#ifdef MDNS_enable +#ifdef MDNS_ENABLED MDNS.addService("http", "tcp", 80); #endif //LittleFS.begin(); @@ -55,6 +57,7 @@ void Web_server_init() { **************************************WEB**************************************** *********************************************************************************/ #ifdef ESP32 + server.addHandler(new SPIFFSEditor(LittleFS, jsonReadStr(configSetupJson, "weblogin").c_str(), jsonReadStr(configSetupJson, "webpass").c_str())); #elif defined(ESP8266) server.addHandler(new SPIFFSEditor(jsonReadStr(configSetupJson, "weblogin").c_str(), jsonReadStr(configSetupJson, "webpass").c_str())); diff --git a/src/WiFiUtils.cpp b/src/WiFiUtils.cpp index 9826710a..2e31d19f 100644 --- a/src/WiFiUtils.cpp +++ b/src/WiFiUtils.cpp @@ -63,8 +63,7 @@ bool StartAPMode() { WiFi.softAP(_ssidAP.c_str(), _passwordAP.c_str()); IPAddress myIP = WiFi.softAPIP(); led_blink("on"); - Serial.print("[I] AP IP: "); - Serial.println(myIP); + Serial.printf("[I] AP IP: %s\n", myIP.toString().c_str()); jsonWriteStr(configSetupJson, "ip", myIP.toString()); //if (jsonReadInt(configOptionJson, "pass_status") != 1) { @@ -89,7 +88,7 @@ boolean RouterFind(String ssid) { Serial.printf("[I][WIFI] scan result = %d\n", n); if (n == -2) { // не было запущено, запускаем - Serial.println("[I][WIFI] scanning has not been triggered, start scanning"); + Serial.println("[I][WIFI] start scanning"); // async, show_hidden WiFi.scanNetworks(true, false); } else if (n == -1) { diff --git a/src/Widgets.cpp b/src/Widgets.cpp index 62ae8edf..43db4c30 100644 --- a/src/Widgets.cpp +++ b/src/Widgets.cpp @@ -18,7 +18,7 @@ void createWidget (String widget_name, String page_name, String page_number, St jsonWriteStr(widget, "descr", widget_name); jsonWriteStr(widget, "topic", prex + "/" + topic); -#ifdef layout_in_ram +#ifdef LAYOUT_IN_RAM all_widgets += widget + "\r\n"; #else addFile("layout.txt", widget); @@ -46,7 +46,7 @@ void createWidgetParam (String widget_name, String page_name, String page_numbe if (name2 != "") jsonWriteStr(widget, name2, param2); if (name3 != "") jsonWriteStr(widget, name3, param3); -#ifdef layout_in_ram +#ifdef LAYOUT_IN_RAM all_widgets += widget + "\r\n"; #else addFile("layout.txt", widget); @@ -72,7 +72,7 @@ void createChart (String widget_name, String page_name, String page_number, Str jsonWriteStr(widget, "maxCount", maxCount); jsonWriteStr(widget, "topic", prex + "/" + topic); -#ifdef layout_in_ram +#ifdef LAYOUT_IN_RAM all_widgets += widget + "\r\n"; #else addFile("layout.txt", widget); diff --git a/src/iot_firmware.cpp b/src/iot_firmware.cpp index 140bae35..f8d90477 100644 --- a/src/iot_firmware.cpp +++ b/src/iot_firmware.cpp @@ -10,7 +10,7 @@ void setup() { setChipId(); - File_system_init(); + fileSystemInit(); Serial.println("[V] LittleFS"); CMD_init(); @@ -40,14 +40,14 @@ void setup() { Time_Init(); Serial.println("[V] Time_Init"); -#ifdef UDP_enable +#ifdef UDP_ENABLED UDP_init(); Serial.println("[V] UDP_init"); #endif ts.add( TEST, 10000, [&](void*) { - getMemoryLoad("[I] sysinfo "); + printMemoryStatus("[I] sysinfo "); }, nullptr, true); @@ -55,7 +55,7 @@ void setup() { } void loop() { -#ifdef OTA_enable +#ifdef OTA_UPDATES_ENABLED ArduinoOTA.handle(); #endif @@ -69,7 +69,7 @@ void loop() { handleCMD_loop(); handleButton(); handleScenario(); -#ifdef UDP_enable +#ifdef UDP_ENABLED handleUdp(); #endif ts.update(); @@ -79,7 +79,7 @@ void not_async_actions() { do_mqtt_connection(); do_upgrade_url(); do_upgrade(); -#ifdef UDP_enable +#ifdef UDP_ENABLED do_udp_data_parse(); do_mqtt_send_settings_to_udp(); #endif diff --git a/src/main.cpp b/src/main.cpp index d054df74..78e1faa6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -50,17 +50,17 @@ void sendCONFIG(String topik, String widgetConfig, String key, String date) { void led_blink(String satus) { #ifdef ESP8266 #ifdef blink_pin - pinMode(blink_pin, OUTPUT); + pinMode(LED_PIN, OUTPUT); if (satus == "off") { - noTone(blink_pin); - digitalWrite(blink_pin, HIGH); + noTone(LED_PIN); + digitalWrite(LED_PIN, HIGH); } if (satus == "on") { - noTone(blink_pin); - digitalWrite(blink_pin, LOW); + noTone(LED_PIN); + digitalWrite(LED_PIN, LOW); } - if (satus == "slow") tone(blink_pin, 1); - if (satus == "fast") tone(blink_pin, 20); + if (satus == "slow") tone(LED_PIN, 1); + if (satus == "fast") tone(LED_PIN, 20); #endif #endif } @@ -68,7 +68,10 @@ void led_blink(String satus) { const String getChipId() { String res; #ifdef ESP32 - res = String(ESP.getEfuseMac()); + char buf[32] = {0}; + uint32_t mac = ESP.getEfuseMac(); + sprintf(buf, "%0X", mac); + res = String(buf); #endif #ifdef ESP8266 res = String(ESP.getChipId()) + "-" + String(ESP.getFlashChipId()); @@ -81,7 +84,7 @@ void setChipId() { Serial.println(chipId); } -void getMemoryLoad(String text) { +void printMemoryStatus(String text) { #ifdef ESP8266 uint32_t all_memory = 52864; #endif diff --git a/src/udp.cpp b/src/udp.cpp index e373bd1a..f1cdddaf 100644 --- a/src/udp.cpp +++ b/src/udp.cpp @@ -18,9 +18,9 @@ unsigned int udp_port = 4210; void handleUdp_esp32(); void add_dev_in_list(String fileName, String id, String dev_name, String ip); -#ifdef UDP_enable +#ifdef UDP_ENABLED void UDP_init() { - LittleFS.remove("/dev.csv"); + removeFile("/dev.csv"); addFile("dev.csv", "device id;device name;ip address"); #ifdef ESP8266 @@ -123,8 +123,8 @@ void do_udp_data_parse() { } void add_dev_in_list(String filename, String id, String dev_name, String ip) { - File configFile = LittleFS.open("/" + filename, "r"); - if (!configFile.find(id.c_str())) { + auto file = seekFile("/" + filename); + if (!file.find(id.c_str())) { addFile(filename, id + ";" + dev_name + "; " + ip + ""); } }