mirror of
https://github.com/esp8266/Arduino.git
synced 2025-04-19 23:22:16 +03:00
* Free space of overwritten files in LittleFS Fixes #7426 LittleFS doesn't update the on-flash data structures when a file is reopened as O_TRUNC until the file is closed. This means the space of the original, inaccessible file cannot be used, causing OOS errors in cases when a large file is being overwritten. Explicitly call the file sync operation to update the on-flash metadata as soon as a file is opened. For normal files it's a no-op, but for O_TRUNC modes it will free the space, allowing full overwrite of large files. * Add host test case for change
406 lines
13 KiB
C++
406 lines
13 KiB
C++
static void createFile (const char* name, const char* content)
|
|
{
|
|
auto f = FSTYPE.open(name, "w");
|
|
REQUIRE(f);
|
|
if (content) {
|
|
f.print(content);
|
|
}
|
|
}
|
|
|
|
static String readFile (const char* name)
|
|
{
|
|
auto f = FSTYPE.open(name, "r");
|
|
if (f) {
|
|
return f.readString();
|
|
}
|
|
return String();
|
|
}
|
|
|
|
static std::set<String> listDir (const char* path)
|
|
{
|
|
std::set<String> result;
|
|
Dir dir = FSTYPE.openDir(path);
|
|
while (dir.next()) {
|
|
REQUIRE(result.find(dir.fileName()) == std::end(result));
|
|
result.insert(dir.fileName());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "FS can begin",TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "FS can't begin with zero size",TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(0, 8, 512, "");
|
|
REQUIRE_FALSE(FSTYPE.begin());
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Before begin is called, open will fail",TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE_FALSE(FSTYPE.open("/foo", "w"));
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "FS can create file",TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/test", "");
|
|
REQUIRE(FSTYPE.exists("/test"));
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Files can be written and appended to",TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
{
|
|
File f = FSTYPE.open("config1.txt", "w");
|
|
REQUIRE(f);
|
|
f.println("file 1");
|
|
}
|
|
{
|
|
File f = FSTYPE.open("config1.txt", "a");
|
|
REQUIRE(f);
|
|
f.println("file 1 again");
|
|
}
|
|
{
|
|
File f = FSTYPE.open("config1.txt", "r");
|
|
REQUIRE(f);
|
|
char buf[128];
|
|
size_t len = f.read((uint8_t*)buf, sizeof(buf));
|
|
buf[len] = 0;
|
|
REQUIRE(strcmp(buf, "file 1\r\nfile 1 again\r\n") == 0);
|
|
}
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Files persist after reset", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("config1.txt", "file 1");
|
|
|
|
FS_MOCK_RESET();
|
|
REQUIRE(FSTYPE.begin());
|
|
REQUIRE(readFile("config1.txt") == "file 1");
|
|
}
|
|
|
|
|
|
TEST_CASE(TESTPRE "Filesystem is empty after format", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.format());
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/1", "first");
|
|
createFile("/2", "second");
|
|
FSTYPE.end();
|
|
REQUIRE(FSTYPE.format());
|
|
REQUIRE(FSTYPE.begin());
|
|
Dir root = FSTYPE.openDir("/");
|
|
size_t count = 0;
|
|
while (root.next()) {
|
|
++count;
|
|
}
|
|
REQUIRE(count == 0);
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "File names which are too long are rejected", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
const char* emptyName = "";
|
|
const char* longName_31 = "/234567890123456789012345678901";
|
|
const char* longName_32 = TOOLONGFILENAME;
|
|
REQUIRE_FALSE(FSTYPE.open(emptyName, "w"));
|
|
REQUIRE_FALSE(FSTYPE.open(emptyName, "r"));
|
|
REQUIRE_FALSE(FSTYPE.exists(emptyName));
|
|
REQUIRE_FALSE(FSTYPE.open(longName_32, "w"));
|
|
REQUIRE_FALSE(FSTYPE.open(longName_32, "r"));
|
|
REQUIRE_FALSE(FSTYPE.exists(longName_32));
|
|
REQUIRE(FSTYPE.open(longName_31, "w"));
|
|
REQUIRE(FSTYPE.open(longName_31, "r"));
|
|
REQUIRE(FSTYPE.exists(longName_31));
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "#1685 Duplicate files", "[fs][bugreport]")
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/config", "some text");
|
|
createFile("/data", "");
|
|
readFile("/config");
|
|
createFile("/data", "more text");
|
|
auto files = listDir("/");
|
|
REQUIRE(files.size() == 2);
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "#1819 Can list all files with openDir(\"\")", "[fs][bugreport]")
|
|
{
|
|
FS_MOCK_DECLARE(96, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/file1", "some text");
|
|
createFile("/file2", "other text");
|
|
createFile("file3", "more text");
|
|
createFile("sorta-dir/file4", "\n");
|
|
auto files = listDir("");
|
|
REQUIRE(files.size() == 4);
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "truncate", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/file1", "some text");
|
|
auto f = FSTYPE.open("/file1", "r+");
|
|
REQUIRE(f.truncate(4));
|
|
f.close();
|
|
String s = readFile("/file1");
|
|
REQUIRE( s == "some" );
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "open(w+) truncates file", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/file1", "some text");
|
|
String s = readFile("/file1");
|
|
REQUIRE( s == "some text");
|
|
auto f = FSTYPE.open("/file1", "w+");
|
|
f.close();
|
|
String t = readFile("/file1");
|
|
REQUIRE( t == "");
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "peek() returns -1 on EOF", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/file1", "some text");
|
|
auto f = FSTYPE.open("/file1", "r+");
|
|
REQUIRE(f.seek(8));
|
|
REQUIRE(f.peek() == 't');
|
|
REQUIRE(f.read() == 't');
|
|
REQUIRE(f.peek() == -1);
|
|
REQUIRE(f.read() == -1);
|
|
f.close();
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "seek() pase EOF returns error (#7323)", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/file1", "some text");
|
|
auto f = FSTYPE.open("/file1", "r+");
|
|
REQUIRE_FALSE(f.seek(10));
|
|
f.close();
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Rewriting file frees space immediately (#7426)", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
FSInfo inf;
|
|
FSTYPE.info(inf);
|
|
// Calculate the size to write per-FS, due to differing overheads
|
|
int kbToWrite = (inf.totalBytes - inf.usedBytes - 8192) / 1024;
|
|
// Create and overwrite a file >50% of spaceA (48/64K)
|
|
for (auto x = 0; x < 2; x++) {
|
|
auto f = FSTYPE.open("/file1.bin", "w");
|
|
REQUIRE(f);
|
|
uint8_t buff[1024];
|
|
memset(buff, 0xaa, 1024);
|
|
for (auto i = 0; i < kbToWrite; i++) {
|
|
REQUIRE(f.write(buff, 1024));
|
|
}
|
|
f.close();
|
|
FSTYPE.info(inf);
|
|
}
|
|
}
|
|
|
|
#ifdef FS_HAS_DIRS
|
|
|
|
#if FSTYPE != SDFS
|
|
// We silently make subdirectories if they do not exist and silently remove
|
|
// them when they're no longer needed, so make sure we can clean up after
|
|
// ourselves. At some point we may drop this and go to normal POSIX mkdir
|
|
// behavior and expose the FS::mkdir() method, but for now this works OK.
|
|
TEST_CASE(TESTPRE "Removing all files in a subdir removes that subdir", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(128, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/empty", "");
|
|
createFile("/not_empty", "some text");
|
|
createFile("/another", "more text");
|
|
createFile("/subdir/empty", "");
|
|
createFile("/subdir/not_empty", "text again");
|
|
auto files = listDir("/");
|
|
REQUIRE(files.size() == 4);
|
|
files = listDir("/subdir");
|
|
REQUIRE(files.size() == 2);
|
|
// Delete one of subdir, should still exist afterwards
|
|
FSTYPE.remove("subdir/empty");
|
|
files = listDir("/subdir");
|
|
REQUIRE(files.size() == 1);
|
|
FSTYPE.remove("subdir/not_empty");
|
|
files = listDir("/subdir");
|
|
REQUIRE(files.size() == 0);
|
|
files = listDir("/");
|
|
REQUIRE(files.size() == 3);
|
|
REQUIRE(files.find("subdir") == std::end(files));
|
|
}
|
|
#endif
|
|
|
|
// LittleFS openDir is slightly different than SPIFFS. In SPIFFS there
|
|
// are no directories and "/" is just another character, so "/a/b/c" is a
|
|
// file in the root dir whose name is "/a/b/c". In LittleFS we have full
|
|
// directory support, so "/a/b/c" is a file "c" in the "/a/b" dir.
|
|
// This means that if you iterate over dirOpen("/") on SPIFFS you get
|
|
// a list of every file, including "subdirs". On LittleFS, you need to
|
|
// explicitly open the subdir to see its files. This behavior is the
|
|
// same as POSIX readdir(), and helps isolate subdirs from each other.
|
|
// Also note that the returned filenames in the "dir.next()" operator
|
|
// will be in that subdir (i.e. if you opendir("/a/b"); f=dir.next();"
|
|
// f.name == "c" and not "/a/b/c" as you would see in SPIFFS.
|
|
TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/empty", "");
|
|
createFile("/not_empty", "some text");
|
|
createFile("/another", "more text");
|
|
createFile("/subdir/empty", "");
|
|
createFile("/subdir/not_empty", "text again");
|
|
auto files = listDir("/");
|
|
REQUIRE(files.size() == 4);
|
|
bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
|
|
REQUIRE(empty);
|
|
bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
|
|
REQUIRE(not_empty);
|
|
bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files));
|
|
REQUIRE(another);
|
|
|
|
files = listDir("/subdir");
|
|
REQUIRE(files.size() == 2);
|
|
bool sub_empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
|
|
REQUIRE(sub_empty);
|
|
bool sub_not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
|
|
REQUIRE(sub_not_empty);
|
|
}
|
|
|
|
File FindFileByName(const File f[], int count, const char *name)
|
|
{
|
|
for (int i=0; i<count; i++) {
|
|
if (!strcmp(name, f[i].name())) return f[i];
|
|
}
|
|
return f[0];
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Listfiles.ino example", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(128, 8, 512, "");
|
|
REQUIRE(FSTYPE.format());
|
|
REQUIRE(FSTYPE.begin());
|
|
|
|
createFile("file1", "hello");
|
|
createFile("file2", "hola");
|
|
createFile("dir1/file3", "nihao");
|
|
createFile("dir2/dir3/file4", "bonjour");
|
|
|
|
File root = FSTYPE.open("/", "r");
|
|
// LittleFS and SDFS are not guaranteed to put the names in order of creation, so
|
|
// manually look for them...
|
|
File f[4];
|
|
f[0] = root.openNextFile();
|
|
f[1] = root.openNextFile();
|
|
f[2] = root.openNextFile();
|
|
f[3] = root.openNextFile();
|
|
File file1 = FindFileByName(f, 4, "file1");
|
|
File file2 = FindFileByName(f, 4, "file2");
|
|
File dir1 = FindFileByName(f, 4, "dir1");
|
|
File dir1_file3 = dir1.openNextFile();
|
|
File dir2 = FindFileByName(f, 4, "dir2");
|
|
File dir2_dir3 = dir2.openNextFile();
|
|
File dir2_dir3_file4 = dir2_dir3.openNextFile();
|
|
|
|
bool ok;
|
|
ok = root.isDirectory() && !root.isFile() && !strcmp(root.name(), "/");
|
|
REQUIRE(ok);
|
|
ok = !file1.isDirectory() && file1.isFile() && !strcmp(file1.name(), "file1");
|
|
REQUIRE(ok);
|
|
ok = !file2.isDirectory() && file2.isFile() && !strcmp(file2.name(), "file2");
|
|
REQUIRE(ok);
|
|
ok = dir1.isDirectory() && !dir1.isFile() && !strcmp(dir1.name(), "dir1");
|
|
REQUIRE(ok);
|
|
ok = !dir1_file3.isDirectory() && dir1_file3.isFile() && !strcmp(dir1_file3.name(), "file3") &&
|
|
!strcmp(dir1_file3.fullName(), "dir1/file3");
|
|
REQUIRE(ok);
|
|
ok = dir2.isDirectory() && !dir2.isFile() && !strcmp(dir2.name(), "dir2");
|
|
REQUIRE(ok);
|
|
ok = dir2_dir3.isDirectory() && !dir2_dir3.isFile() && !strcmp(dir2_dir3.name(), "dir3");
|
|
REQUIRE(ok);
|
|
ok = !dir2_dir3_file4.isDirectory() && dir2_dir3_file4.isFile() && !strcmp(dir2_dir3_file4.name(), "file4") &&
|
|
!strcmp(dir2_dir3_file4.fullName(), "dir2/dir3/file4");
|
|
REQUIRE(ok);
|
|
|
|
REQUIRE(readFile("/file1") == "hello");
|
|
REQUIRE(readFile("file2") == "hola");
|
|
REQUIRE(readFile("dir1/file3") == "nihao");
|
|
REQUIRE(readFile("/dir2/dir3/file4") == "bonjour");
|
|
}
|
|
|
|
#else // !FS_HAS_DIRS
|
|
|
|
TEST_CASE(TESTPRE "Dir lists all files", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
createFile("/empty", "");
|
|
createFile("/not_empty", "some text");
|
|
createFile("/another", "more text");
|
|
createFile("/subdir/empty", "");
|
|
createFile("/subdir/not_empty", "text again");
|
|
auto files = listDir("/");
|
|
REQUIRE(files.size() == 5);
|
|
bool empty = (files.find("/empty") != std::end(files)) || (files.find("empty") != std::end(files));
|
|
REQUIRE(empty);
|
|
bool not_empty = (files.find("/not_empty") != std::end(files)) || (files.find("not_empty") != std::end(files));
|
|
REQUIRE(not_empty);
|
|
bool another = (files.find("/another") != std::end(files)) || (files.find("another") != std::end(files));
|
|
REQUIRE(another);
|
|
bool sub_empty = (files.find("/subdir/empty") != std::end(files)) || (files.find("subdir/empty") != std::end(files));
|
|
REQUIRE(sub_empty);
|
|
bool sub_not_empty = (files.find("/subdir/not_empty") != std::end(files)) || (files.find("subdir/not_empty") != std::end(files));
|
|
REQUIRE(sub_not_empty);
|
|
}
|
|
|
|
TEST_CASE(TESTPRE "Multisplendored File::writes", TESTPAT)
|
|
{
|
|
FS_MOCK_DECLARE(64, 8, 512, "");
|
|
REQUIRE(FSTYPE.begin());
|
|
|
|
File f = FSTYPE.open("/file.txt", "w");
|
|
f.write('a');
|
|
f.write(65);
|
|
f.write("bbcc");
|
|
f.write("theend", 6);
|
|
char block[3]={'x','y','z'};
|
|
f.write(block, 3);
|
|
uint32_t bigone = 0x40404040;
|
|
f.write((const uint8_t*)&bigone, 4);
|
|
f.close();
|
|
REQUIRE(readFile("/file.txt") == "aAbbcctheendxyz@@@@");
|
|
File g = FSTYPE.open("/file.txt", "w");
|
|
g.write(0);
|
|
g.close();
|
|
g = FSTYPE.open("/file.txt", "r");
|
|
uint8_t u = 0x66;
|
|
g.read(&u, 1);
|
|
g.close();
|
|
REQUIRE(u == 0);
|
|
}
|
|
|
|
#endif
|