Skip to content

Commit 2936514

Browse files
committed
Added atomic move using dirty tag in entry type
The "move problem" has been present in littlefs for a while, but I haven't come across a solution worth implementing for various reasons. The problem is simple: how do we move directory entries across directories atomically? Since multiple directory entries are involved, we can't rely entirely on the atomic block updates. It ends up being a bit of a puzzle. To make the problem more complicated, any directory block update can fail due to wear, and cause the directory block to need to be relocated. This happens rarely, but brings a large number of corner cases. --- The solution in this patch is simple: 1. Mark source as "moving" 2. Copy source to destination 3. Remove source If littlefs ever runs into a "moving" entry, that means a power loss occured during a move. Either the destination entry exists or it doesn't. In this case we just search the entire filesystem for the destination entry. This is expensive, however the chance of a power loss during a move is relatively low.
1 parent ac9766e commit 2936514

File tree

4 files changed

+438
-38
lines changed

4 files changed

+438
-38
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ size: $(OBJ)
3232

3333
.SUFFIXES:
3434
test: test_format test_dirs test_files test_seek test_parallel \
35-
test_alloc test_paths test_orphan test_corrupt
35+
test_alloc test_paths test_orphan test_move test_corrupt
3636
test_%: tests/test_%.sh
3737
./$<
3838

lfs.c

Lines changed: 196 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,11 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
253253
static int lfs_pred(lfs_t *lfs, const lfs_block_t dir[2], lfs_dir_t *pdir);
254254
static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
255255
lfs_dir_t *parent, lfs_entry_t *entry);
256+
static int lfs_moved(lfs_t *lfs, const void *e);
256257
static int lfs_relocate(lfs_t *lfs,
257258
const lfs_block_t oldpair[2], const lfs_block_t newpair[2]);
258259
int lfs_deorphan(lfs_t *lfs);
260+
int lfs_deduplicate(lfs_t *lfs);
259261

260262

261263
/// Block allocator ///
@@ -722,8 +724,8 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
722724
return err;
723725
}
724726

725-
if ((entry->d.type != LFS_TYPE_REG &&
726-
entry->d.type != LFS_TYPE_DIR) ||
727+
if (((0x7f & entry->d.type) != LFS_TYPE_REG &&
728+
(0x7f & entry->d.type) != LFS_TYPE_DIR) ||
727729
entry->d.nlen != pathlen) {
728730
continue;
729731
}
@@ -741,6 +743,16 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
741743
}
742744
}
743745

746+
// check that entry has not been moved
747+
if (entry->d.type & 0x80) {
748+
int moved = lfs_moved(lfs, &entry->d.u);
749+
if (moved < 0 || moved) {
750+
return (moved < 0) ? moved : LFS_ERR_NOENT;
751+
}
752+
753+
entry->d.type &= ~0x80;
754+
}
755+
744756
pathname += pathlen;
745757
pathname += strspn(pathname, "/");
746758
if (pathname[0] == '\0') {
@@ -764,6 +776,14 @@ static int lfs_dir_find(lfs_t *lfs, lfs_dir_t *dir,
764776

765777
/// Top level directory operations ///
766778
int lfs_mkdir(lfs_t *lfs, const char *path) {
779+
// make sure directories are clean
780+
if (!lfs->deduplicated) {
781+
int err = lfs_deduplicate(lfs);
782+
if (err) {
783+
return err;
784+
}
785+
}
786+
767787
// fetch parent directory
768788
lfs_dir_t cwd;
769789
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
@@ -880,10 +900,26 @@ int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
880900
return (err == LFS_ERR_NOENT) ? 0 : err;
881901
}
882902

883-
if (entry.d.type == LFS_TYPE_REG ||
884-
entry.d.type == LFS_TYPE_DIR) {
885-
break;
903+
if ((0x7f & entry.d.type) != LFS_TYPE_REG &&
904+
(0x7f & entry.d.type) != LFS_TYPE_DIR) {
905+
continue;
906+
}
907+
908+
// check that entry has not been moved
909+
if (entry.d.type & 0x80) {
910+
int moved = lfs_moved(lfs, &entry.d.u);
911+
if (moved < 0) {
912+
return moved;
913+
}
914+
915+
if (moved) {
916+
continue;
917+
}
918+
919+
entry.d.type &= ~0x80;
886920
}
921+
922+
break;
887923
}
888924

889925
info->type = entry.d.type;
@@ -1113,6 +1149,14 @@ static int lfs_index_traverse(lfs_t *lfs,
11131149
/// Top level file operations ///
11141150
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
11151151
const char *path, int flags) {
1152+
// make sure directories are clean
1153+
if ((flags & 3) != LFS_O_RDONLY && !lfs->deduplicated) {
1154+
int err = lfs_deduplicate(lfs);
1155+
if (err) {
1156+
return err;
1157+
}
1158+
}
1159+
11161160
// allocate entry for file if it doesn't exist
11171161
lfs_dir_t cwd;
11181162
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
@@ -1598,6 +1642,14 @@ int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
15981642
}
15991643

16001644
int lfs_remove(lfs_t *lfs, const char *path) {
1645+
// make sure directories are clean
1646+
if (!lfs->deduplicated) {
1647+
int err = lfs_deduplicate(lfs);
1648+
if (err) {
1649+
return err;
1650+
}
1651+
}
1652+
16011653
lfs_dir_t cwd;
16021654
int err = lfs_dir_fetch(lfs, &cwd, lfs->root);
16031655
if (err) {
@@ -1654,6 +1706,14 @@ int lfs_remove(lfs_t *lfs, const char *path) {
16541706
}
16551707

16561708
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
1709+
// make sure directories are clean
1710+
if (!lfs->deduplicated) {
1711+
int err = lfs_deduplicate(lfs);
1712+
if (err) {
1713+
return err;
1714+
}
1715+
}
1716+
16571717
// find old entry
16581718
lfs_dir_t oldcwd;
16591719
int err = lfs_dir_fetch(lfs, &oldcwd, lfs->root);
@@ -1667,6 +1727,14 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
16671727
return err;
16681728
}
16691729

1730+
// mark as moving
1731+
oldentry.d.type |= 0x80;
1732+
err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL);
1733+
if (err) {
1734+
return err;
1735+
}
1736+
oldentry.d.type &= ~0x80;
1737+
16701738
// allocate new entry
16711739
lfs_dir_t newcwd;
16721740
err = lfs_dir_fetch(lfs, &newcwd, lfs->root);
@@ -1716,35 +1784,6 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
17161784
}
17171785
}
17181786

1719-
// fetch again in case newcwd == oldcwd
1720-
err = lfs_dir_fetch(lfs, &oldcwd, oldcwd.pair);
1721-
if (err) {
1722-
return err;
1723-
}
1724-
1725-
err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath);
1726-
if (err) {
1727-
return err;
1728-
}
1729-
1730-
// remove from old location
1731-
err = lfs_dir_remove(lfs, &oldcwd, &oldentry);
1732-
if (err) {
1733-
return err;
1734-
}
1735-
1736-
// shift over any files that are affected
1737-
for (lfs_file_t *f = lfs->files; f; f = f->next) {
1738-
if (lfs_paircmp(f->pair, oldcwd.pair) == 0) {
1739-
if (f->poff == oldentry.off) {
1740-
f->pair[0] = 0xffffffff;
1741-
f->pair[1] = 0xffffffff;
1742-
} else if (f->poff > oldentry.off) {
1743-
f->poff -= lfs_entry_size(&oldentry);
1744-
}
1745-
}
1746-
}
1747-
17481787
// if we were a directory, just run a deorphan step, this should
17491788
// collect us, although is expensive
17501789
if (prevexists && preventry.d.type == LFS_TYPE_DIR) {
@@ -1754,6 +1793,12 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
17541793
}
17551794
}
17561795

1796+
// just deduplicate
1797+
err = lfs_deduplicate(lfs);
1798+
if (err) {
1799+
return err;
1800+
}
1801+
17571802
return 0;
17581803
}
17591804

@@ -1802,6 +1847,7 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
18021847
lfs->root[1] = 0xffffffff;
18031848
lfs->files = NULL;
18041849
lfs->deorphaned = false;
1850+
lfs->deduplicated = false;
18051851

18061852
return 0;
18071853
}
@@ -1979,7 +2025,7 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data) {
19792025
}
19802026

19812027
dir.off += lfs_entry_size(&entry);
1982-
if ((0xf & entry.d.type) == (0xf & LFS_TYPE_REG)) {
2028+
if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) {
19832029
int err = lfs_index_traverse(lfs, &lfs->rcache, NULL,
19842030
entry.d.u.file.head, entry.d.u.file.size, cb, data);
19852031
if (err) {
@@ -2069,7 +2115,7 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
20692115
break;
20702116
}
20712117

2072-
if (((0xf & entry->d.type) == (0xf & LFS_TYPE_DIR)) &&
2118+
if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) &&
20732119
lfs_paircmp(entry->d.u.dir, dir) == 0) {
20742120
return true;
20752121
}
@@ -2079,6 +2125,46 @@ static int lfs_parent(lfs_t *lfs, const lfs_block_t dir[2],
20792125
return false;
20802126
}
20812127

2128+
static int lfs_moved(lfs_t *lfs, const void *e) {
2129+
if (lfs_pairisnull(lfs->root)) {
2130+
return 0;
2131+
}
2132+
2133+
// skip superblock
2134+
lfs_dir_t cwd;
2135+
int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
2136+
if (err) {
2137+
return err;
2138+
}
2139+
2140+
// iterate over all directory directory entries
2141+
lfs_entry_t entry;
2142+
while (!lfs_pairisnull(cwd.d.tail)) {
2143+
int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
2144+
if (err) {
2145+
return err;
2146+
}
2147+
2148+
while (true) {
2149+
int err = lfs_dir_next(lfs, &cwd, &entry);
2150+
if (err && err != LFS_ERR_NOENT) {
2151+
return err;
2152+
}
2153+
2154+
if (err == LFS_ERR_NOENT) {
2155+
break;
2156+
}
2157+
2158+
if (!(0x80 & entry.d.type) &&
2159+
memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) {
2160+
return true;
2161+
}
2162+
}
2163+
}
2164+
2165+
return false;
2166+
}
2167+
20822168
static int lfs_relocate(lfs_t *lfs,
20832169
const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) {
20842170
// find parent
@@ -2197,3 +2283,77 @@ int lfs_deorphan(lfs_t *lfs) {
21972283
return 0;
21982284
}
21992285

2286+
int lfs_deduplicate(lfs_t *lfs) {
2287+
lfs->deduplicated = true;
2288+
2289+
if (lfs_pairisnull(lfs->root)) {
2290+
return 0;
2291+
}
2292+
2293+
// skip superblock
2294+
lfs_dir_t cwd;
2295+
int err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){0, 1});
2296+
if (err) {
2297+
return err;
2298+
}
2299+
2300+
// iterate over all directory directory entries
2301+
lfs_entry_t entry;
2302+
while (!lfs_pairisnull(cwd.d.tail)) {
2303+
int err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail);
2304+
if (err) {
2305+
return err;
2306+
}
2307+
2308+
while (true) {
2309+
int err = lfs_dir_next(lfs, &cwd, &entry);
2310+
if (err && err != LFS_ERR_NOENT) {
2311+
return err;
2312+
}
2313+
2314+
if (err == LFS_ERR_NOENT) {
2315+
break;
2316+
}
2317+
2318+
// found moved entry
2319+
if (entry.d.type & 0x80) {
2320+
int moved = lfs_moved(lfs, &entry.d.u);
2321+
if (moved < 0) {
2322+
return moved;
2323+
}
2324+
2325+
if (moved) {
2326+
LFS_DEBUG("Found move %d %d",
2327+
entry.d.u.dir[0], entry.d.u.dir[1]);
2328+
int err = lfs_dir_remove(lfs, &cwd, &entry);
2329+
if (err) {
2330+
return err;
2331+
}
2332+
2333+
// shift over any files that are affected
2334+
for (lfs_file_t *f = lfs->files; f; f = f->next) {
2335+
if (lfs_paircmp(f->pair, cwd.pair) == 0) {
2336+
if (f->poff == entry.off) {
2337+
f->pair[0] = 0xffffffff;
2338+
f->pair[1] = 0xffffffff;
2339+
} else if (f->poff > entry.off) {
2340+
f->poff -= lfs_entry_size(&entry);
2341+
}
2342+
}
2343+
}
2344+
} else {
2345+
LFS_DEBUG("Found partial move %d %d",
2346+
entry.d.u.dir[0], entry.d.u.dir[1]);
2347+
entry.d.type &= ~0x80;
2348+
int err = lfs_dir_update(lfs, &cwd, &entry, NULL);
2349+
if (err) {
2350+
return err;
2351+
}
2352+
}
2353+
}
2354+
}
2355+
}
2356+
2357+
return 0;
2358+
}
2359+

lfs.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ enum lfs_error {
4646
enum lfs_type {
4747
LFS_TYPE_REG = 0x11,
4848
LFS_TYPE_DIR = 0x22,
49-
LFS_TYPE_SUPERBLOCK = 0xe2,
49+
LFS_TYPE_SUPERBLOCK = 0x2e,
5050
};
5151

5252
// File open flags
@@ -244,6 +244,7 @@ typedef struct lfs {
244244

245245
lfs_free_t free;
246246
bool deorphaned;
247+
bool deduplicated;
247248
} lfs_t;
248249

249250

@@ -434,5 +435,8 @@ int lfs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
434435
// Returns a negative error code on failure.
435436
int lfs_deorphan(lfs_t *lfs);
436437

438+
// TODO doc
439+
int lfs_deduplicate(lfs_t *lfs);
440+
437441

438442
#endif

0 commit comments

Comments
 (0)