Skip to content

Commit 1a58ba7

Browse files
committed
Fixed ENOSPC issues with zero-granularity blocks
Result of testing on zero-granularity blocks, where the prog size and read size equals the block size. This represents SD cards and other traditional forms of block storage where we don't really get a benefit from the metadata logging. Unfortunately, since updates in both are tested by the same script, we can't really use simple bash commands. Added a more complex script to simulate corruption. Fortunately this should be more robust than the previous solutions. The main fixes were around corner cases where the commit logic fell apart when it didn't have room to complete commits, but these were fixable in the current design.
1 parent 105907b commit 1a58ba7

File tree

4 files changed

+125
-65
lines changed

4 files changed

+125
-65
lines changed

lfs.c

Lines changed: 79 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -886,6 +886,11 @@ static int lfs_dir_compact(lfs_t *lfs,
886886
// drop caches and create tail
887887
lfs->pcache.block = 0xffffffff;
888888

889+
if (ack == -1) {
890+
// If we can't fit in this block, we won't fit in next block
891+
return LFS_ERR_NOSPC;
892+
}
893+
889894
lfs_mdir_t tail;
890895
int err = lfs_dir_alloc(lfs, &tail, dir->split, dir->tail);
891896
if (err) {
@@ -1971,49 +1976,53 @@ int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
19711976
}
19721977

19731978
static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
1974-
relocate:;
1975-
// just relocate what exists into new block
1976-
lfs_block_t nblock;
1977-
int err = lfs_alloc(lfs, &nblock);
1978-
if (err) {
1979-
return err;
1980-
}
1981-
1982-
err = lfs_bd_erase(lfs, nblock);
1983-
if (err) {
1984-
if (err == LFS_ERR_CORRUPT) {
1985-
goto relocate;
1986-
}
1987-
return err;
1988-
}
1989-
1990-
// either read from dirty cache or disk
1991-
for (lfs_off_t i = 0; i < file->off; i++) {
1992-
uint8_t data;
1993-
err = lfs_cache_read(lfs, &lfs->rcache, &file->cache,
1994-
file->block, i, &data, 1);
1979+
while (true) {
1980+
// just relocate what exists into new block
1981+
lfs_block_t nblock;
1982+
int err = lfs_alloc(lfs, &nblock);
19951983
if (err) {
19961984
return err;
19971985
}
19981986

1999-
err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache,
2000-
nblock, i, &data, 1);
1987+
err = lfs_bd_erase(lfs, nblock);
20011988
if (err) {
20021989
if (err == LFS_ERR_CORRUPT) {
20031990
goto relocate;
20041991
}
20051992
return err;
20061993
}
2007-
}
20081994

2009-
// copy over new state of file
2010-
memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
2011-
file->cache.block = lfs->pcache.block;
2012-
file->cache.off = lfs->pcache.off;
2013-
lfs->pcache.block = 0xffffffff;
1995+
// either read from dirty cache or disk
1996+
for (lfs_off_t i = 0; i < file->off; i++) {
1997+
uint8_t data;
1998+
err = lfs_cache_read(lfs, &lfs->rcache, &file->cache,
1999+
file->block, i, &data, 1);
2000+
if (err) {
2001+
return err;
2002+
}
20142003

2015-
file->block = nblock;
2016-
return 0;
2004+
err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache,
2005+
nblock, i, &data, 1);
2006+
if (err) {
2007+
if (err == LFS_ERR_CORRUPT) {
2008+
goto relocate;
2009+
}
2010+
return err;
2011+
}
2012+
}
2013+
2014+
// copy over new state of file
2015+
memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size);
2016+
file->cache.block = lfs->pcache.block;
2017+
file->cache.off = lfs->pcache.off;
2018+
lfs->pcache.block = 0xffffffff;
2019+
2020+
file->block = nblock;
2021+
return 0;
2022+
2023+
relocate:
2024+
continue;
2025+
}
20172026
}
20182027

20192028
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
@@ -2067,6 +2076,7 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
20672076
}
20682077

20692078
break;
2079+
20702080
relocate:
20712081
LFS_DEBUG("Bad block at %d", file->block);
20722082
err = lfs_file_relocate(lfs, file);
@@ -2091,48 +2101,58 @@ static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
20912101
}
20922102

20932103
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
2094-
int err = lfs_file_flush(lfs, file);
2095-
if (err) {
2096-
return err;
2097-
}
2098-
2099-
if ((file->flags & LFS_F_DIRTY) &&
2100-
!(file->flags & LFS_F_ERRED) &&
2101-
!lfs_pairisnull(file->pair)) {
2102-
// update dir entry
2103-
// TODO keep list of dirs including these guys for no
2104-
// need of another reload?
2105-
lfs_mdir_t cwd;
2106-
err = lfs_dir_fetch(lfs, &cwd, file->pair);
2104+
while (true) {
2105+
int err = lfs_file_flush(lfs, file);
21072106
if (err) {
21082107
return err;
21092108
}
21102109

2111-
// either update the references or inline the whole file
2112-
if (!(file->flags & LFS_F_INLINE)) {
2113-
int err = lfs_dir_commit(lfs, &cwd,
2114-
LFS_MKATTR(LFS_TYPE_CTZSTRUCT, file->id,
2115-
&file->ctz.head, sizeof(file->ctz),
2116-
LFS_MKATTR(LFS_FROM_ATTRS, file->id, file->cfg->attrs, 0,
2117-
NULL)));
2110+
if ((file->flags & LFS_F_DIRTY) &&
2111+
!(file->flags & LFS_F_ERRED) &&
2112+
!lfs_pairisnull(file->pair)) {
2113+
// update dir entry
2114+
// TODO keep list of dirs including these guys for no
2115+
// need of another reload?
2116+
lfs_mdir_t cwd;
2117+
err = lfs_dir_fetch(lfs, &cwd, file->pair);
21182118
if (err) {
21192119
return err;
21202120
}
2121-
} else {
2121+
2122+
// either update the references or inline the whole file
21222123
int err = lfs_dir_commit(lfs, &cwd,
2123-
LFS_MKATTR(LFS_TYPE_INLINESTRUCT, file->id,
2124-
file->cache.buffer, file->ctz.size,
21252124
LFS_MKATTR(LFS_FROM_ATTRS, file->id, file->cfg->attrs, 0,
2126-
NULL)));
2125+
(file->flags & LFS_F_INLINE) ?
2126+
LFS_MKATTR(LFS_TYPE_INLINESTRUCT, file->id,
2127+
file->cache.buffer, file->ctz.size, NULL) :
2128+
LFS_MKATTR(LFS_TYPE_CTZSTRUCT, file->id,
2129+
&file->ctz.head, sizeof(file->ctz), NULL)));
21272130
if (err) {
2131+
if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) {
2132+
goto relocate;
2133+
}
21282134
return err;
21292135
}
2136+
2137+
file->flags &= ~LFS_F_DIRTY;
21302138
}
21312139

2132-
file->flags &= ~LFS_F_DIRTY;
2133-
}
2140+
return 0;
21342141

2135-
return 0;
2142+
relocate:
2143+
// inline file doesn't fit anymore
2144+
file->block = 0xfffffffe;
2145+
file->off = file->pos;
2146+
2147+
lfs_alloc_ack(lfs);
2148+
err = lfs_file_relocate(lfs, file);
2149+
if (err) {
2150+
return err;
2151+
}
2152+
2153+
file->flags &= ~LFS_F_INLINE;
2154+
file->flags |= LFS_F_WRITING;
2155+
}
21362156
}
21372157

21382158
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
@@ -3304,6 +3324,7 @@ static int32_t lfs_parent(lfs_t *lfs, const lfs_block_t pair[2],
33043324
// TODO rename to lfs_dir_relocate?
33053325
static int lfs_relocate(lfs_t *lfs,
33063326
const lfs_block_t oldpair[2], const lfs_block_t newpair[2]) {
3327+
// TODO name lfs_dir_relocate?
33073328
// find parent
33083329
lfs_mdir_t parent;
33093330
int32_t tag = lfs_parent(lfs, oldpair, &parent);

tests/corrupt.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env python
2+
3+
import struct
4+
import sys
5+
import os
6+
7+
def main(*paths):
8+
# find most recent block
9+
file = None
10+
rev = None
11+
for path in paths:
12+
try:
13+
nfile = open(path, 'r+b')
14+
nrev, = struct.unpack('<I', nfile.read(4))
15+
16+
assert rev != nrev
17+
if not file or ((rev - nrev) & 0x80000000):
18+
file = nfile
19+
rev = nrev
20+
except IOError:
21+
pass
22+
23+
# go to last commit
24+
tag = 0
25+
while True:
26+
try:
27+
ntag, = struct.unpack('<I', file.read(4))
28+
except struct.error:
29+
break
30+
31+
tag ^= ntag
32+
file.seek(tag & 0xfff, os.SEEK_CUR)
33+
34+
# lob off last 3 bytes
35+
file.seek(-((tag & 0xfff) + 3), os.SEEK_CUR)
36+
file.truncate()
37+
38+
if __name__ == "__main__":
39+
main(*sys.argv[1:])

tests/test_move.sh

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ tests/test.py << TEST
5959
lfs_rename(&lfs, "b/hello", "c/hello") => 0;
6060
lfs_unmount(&lfs) => 0;
6161
TEST
62-
truncate -s-7 blocks/6
62+
tests/corrupt.py blocks/{6,7}
6363
tests/test.py << TEST
6464
lfs_mount(&lfs, &cfg) => 0;
6565
lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -86,8 +86,8 @@ tests/test.py << TEST
8686
lfs_rename(&lfs, "c/hello", "d/hello") => 0;
8787
lfs_unmount(&lfs) => 0;
8888
TEST
89-
truncate -s-7 blocks/8
90-
truncate -s-7 blocks/a
89+
tests/corrupt.py blocks/{8,9}
90+
tests/corrupt.py blocks/{a,b}
9191
tests/test.py << TEST
9292
lfs_mount(&lfs, &cfg) => 0;
9393
lfs_dir_open(&lfs, &dir[0], "c") => 0;
@@ -166,7 +166,7 @@ tests/test.py << TEST
166166
lfs_rename(&lfs, "b/hi", "c/hi") => 0;
167167
lfs_unmount(&lfs) => 0;
168168
TEST
169-
truncate -s-7 blocks/7
169+
tests/corrupt.py blocks/{6,7}
170170
tests/test.py << TEST
171171
lfs_mount(&lfs, &cfg) => 0;
172172
lfs_dir_open(&lfs, &dir[0], "b") => 0;
@@ -193,8 +193,8 @@ tests/test.py << TEST
193193
lfs_rename(&lfs, "c/hi", "d/hi") => 0;
194194
lfs_unmount(&lfs) => 0;
195195
TEST
196-
truncate -s-7 blocks/9
197-
truncate -s-7 blocks/b
196+
tests/corrupt.py blocks/{8,9}
197+
tests/corrupt.py blocks/{a,b}
198198
tests/test.py << TEST
199199
lfs_mount(&lfs, &cfg) => 0;
200200
lfs_dir_open(&lfs, &dir[0], "c") => 0;

tests/test_orphan.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ tests/test.py << TEST
1717
TEST
1818
# corrupt most recent commit, this should be the update to the previous
1919
# linked-list entry and should orphan the child
20-
truncate -s-14 blocks/8
20+
tests/corrupt.py blocks/{8,9}
2121
tests/test.py << TEST
2222
lfs_mount(&lfs, &cfg) => 0;
2323

0 commit comments

Comments
 (0)