Skip to content

Commit c00abae

Browse files
Support removal of non-empty folders via lfs_remove_recursive(...)
Introduce a function that removes a folder and all of its child folders recursively.
1 parent dd12a7a commit c00abae

File tree

3 files changed

+168
-36
lines changed

3 files changed

+168
-36
lines changed

lfs.c

Lines changed: 89 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3903,11 +3903,7 @@ static int lfs_dir_prep_remove_nonempty_folders(lfs_t *lfs, struct lfs_remove_st
39033903
uint16_t id = 0;
39043904

39053905
// Walk tags stored in this directory and check for any directory
3906-
// tags. Removal of directories with a directory in them can lead
3907-
// to additional orphans in the filesystem, so we return
3908-
// LFS_ERR_NOTEMPTY in this case. Otherwise, leave the loaded
3909-
// directory for the tail end of the directory split to leave a proper
3910-
// view of the filesystem after removal.
3906+
// tags.
39113907
while(true) {
39123908
if (p->dir.m.count == id) {
39133909
if (!p->dir.m.split) {
@@ -3935,7 +3931,11 @@ static int lfs_dir_prep_remove_nonempty_folders(lfs_t *lfs, struct lfs_remove_st
39353931
}
39363932

39373933
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
3938-
// We're not allowed to find leafs
3934+
// In case we are finding leafs, we need to update our tag, too,
3935+
// so that the upper routine knows where this folder was in the
3936+
// parent folder. The parent routine will decide if we need
3937+
// to return LFS_ERR_NOTEMPTY all the way back out.
3938+
p->tag = tag;
39393939
return LFS_ERR_NOTEMPTY;
39403940
}
39413941

@@ -3947,10 +3947,15 @@ static int lfs_dir_prep_remove_nonempty_folders(lfs_t *lfs, struct lfs_remove_st
39473947
#endif
39483948

39493949
#ifndef LFS_READONLY
3950+
// Note: This returns 1 when it had to descend to find a leaf node,
3951+
// 0 when it didn't have to descend.
39503952
static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p,
3951-
lfs_dir_prep_helper_t helper) {
3953+
lfs_dir_prep_helper_t helper, bool find_leaf) {
3954+
lfs_block_t depth = 0;
3955+
lfs_stag_t res;
39523956

3953-
lfs_stag_t res = lfs_dir_get(lfs, &p->cwd, LFS_MKTAG(0x700, 0x3ff, 0),
3957+
new_leaf:
3958+
res = lfs_dir_get(lfs, &p->cwd, LFS_MKTAG(0x700, 0x3ff, 0),
39543959
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(p->tag), 8), p->dir_head);
39553960
if (res < 0) {
39563961
return (int)res;
@@ -3972,7 +3977,20 @@ static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p,
39723977
}
39733978

39743979
err = helper(lfs, p);
3975-
if (err) {
3980+
if (err == LFS_ERR_NOTEMPTY && find_leaf) {
3981+
if (depth++ > lfs->cfg->block_count / 2) {
3982+
return LFS_ERR_CORRUPT;
3983+
}
3984+
3985+
// Descend CWD
3986+
memcpy(&p->cwd, &p->dir.m, sizeof(p->cwd));
3987+
3988+
// And loop
3989+
goto new_leaf;
3990+
}
3991+
3992+
if (err)
3993+
{
39763994
return err;
39773995
}
39783996
}
@@ -3989,7 +4007,7 @@ static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p,
39894007
p->dir.id = 0;
39904008
lfs->mlist = &p->dir;
39914009

3992-
return 0;
4010+
return depth != 0;
39934011
}
39944012
#endif
39954013

@@ -4021,42 +4039,61 @@ static int lfs_dir_finish_removal(lfs_t *lfs, struct lfs_remove_state *p)
40214039
#endif
40224040

40234041
#ifndef LFS_READONLY
4024-
static int lfs_remove_(lfs_t *lfs, const char *path, lfs_dir_prep_helper_t helper) {
4042+
static int lfs_remove_(lfs_t *lfs, const char *path, lfs_dir_prep_helper_t helper, bool recursive) {
4043+
// For the recursive flag to work, we must have the helper defined.
4044+
if (recursive && NULL == helper) {
4045+
return LFS_ERR_INVAL;
4046+
}
4047+
40254048
// deorphan if we haven't yet, needed at most once after poweron
40264049
int err = lfs_fs_forceconsistency(lfs);
40274050
if (err) {
40284051
return err;
40294052
}
40304053

4054+
bool at_toplevel = true;
40314055
struct lfs_remove_state s;
4032-
s.tag = lfs_dir_find(lfs, &s.cwd, &path, NULL);
4033-
if (s.tag < 0 || lfs_tag_id(s.tag) == 0x3ff) {
4034-
return (s.tag < 0) ? (int)s.tag : LFS_ERR_INVAL;
4035-
}
4056+
do {
4057+
// Since we want to keep our in-memory usage minimal, we rely on
4058+
// the disk structure to "recurse". We'll go back to the top
4059+
// level path every time we need to find a new leaf. Technically
4060+
// we could remember the top-level path state so we don't have
4061+
// to search again for this folder entry...
4062+
at_toplevel = true;
4063+
s.tag = lfs_dir_find(lfs, &s.cwd, &path, NULL);
4064+
if (s.tag < 0 || lfs_tag_id(s.tag) == 0x3ff) {
4065+
return (s.tag < 0) ? (int)s.tag : LFS_ERR_INVAL;
4066+
}
4067+
4068+
s.dir.next = lfs->mlist;
4069+
if (lfs_tag_type3(s.tag) == LFS_TYPE_DIR) {
4070+
err = lfs_dir_prep_removal(lfs, &s, helper, recursive);
4071+
if (err == 1) {
4072+
// We had to switch folders and we're no longer in the top-level folder.
4073+
// This means we need to go back from the top after we finish the
4074+
// removal process.
4075+
at_toplevel = false;
4076+
} else if (err < 0) {
4077+
return err;
4078+
}
4079+
}
40364080

4037-
s.dir.next = lfs->mlist;
4038-
if (lfs_tag_type3(s.tag) == LFS_TYPE_DIR) {
4039-
err = lfs_dir_prep_removal(lfs, &s, helper);
4040-
if (err < 0) {
4081+
// delete the entry
4082+
err = lfs_dir_commit(lfs, &s.cwd, LFS_MKATTRS(
4083+
{LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(s.tag), 0), NULL}));
4084+
if (err) {
4085+
lfs->mlist = s.dir.next;
40414086
return err;
40424087
}
4043-
}
40444088

4045-
// delete the entry
4046-
err = lfs_dir_commit(lfs, &s.cwd, LFS_MKATTRS(
4047-
{LFS_MKTAG(LFS_TYPE_DELETE, lfs_tag_id(s.tag), 0), NULL}));
4048-
if (err) {
40494089
lfs->mlist = s.dir.next;
4050-
return err;
4051-
}
4052-
4053-
lfs->mlist = s.dir.next;
4054-
if (lfs_tag_type3(s.tag) == LFS_TYPE_DIR) {
4055-
err = lfs_dir_finish_removal(lfs, &s);
4056-
if (err) {
4057-
return err;
4090+
if (lfs_tag_type3(s.tag) == LFS_TYPE_DIR) {
4091+
err = lfs_dir_finish_removal(lfs, &s);
4092+
if (err) {
4093+
return err;
4094+
}
40584095
}
4059-
}
4096+
} while(!at_toplevel);
40604097

40614098
return 0;
40624099
}
@@ -4119,7 +4156,7 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath,
41194156
// we're renaming to ourselves??
41204157
return 0;
41214158
} else if (lfs_tag_type3(n.tag) == LFS_TYPE_DIR) {
4122-
err = lfs_dir_prep_removal(lfs, &n, helper);
4159+
err = lfs_dir_prep_removal(lfs, &n, helper, false);
41234160
if (err) {
41244161
return err;
41254162
}
@@ -6089,14 +6126,30 @@ int lfs_remove(lfs_t *lfs, const char *path) {
60896126
}
60906127
LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
60916128

6092-
err = lfs_remove_(lfs, path, NULL);
6129+
err = lfs_remove_(lfs, path, NULL, false);
60936130

60946131
LFS_TRACE("lfs_remove -> %d", err);
60956132
LFS_UNLOCK(lfs->cfg);
60966133
return err;
60976134
}
60986135
#endif
60996136

6137+
#ifndef LFS_READONLY
6138+
int lfs_remove_recursive(lfs_t *lfs, const char *path) {
6139+
int err = LFS_LOCK(lfs->cfg);
6140+
if (err) {
6141+
return err;
6142+
}
6143+
LFS_TRACE("lfs_remove_recursive(%p, \"%s\")", (void*)lfs, path);
6144+
6145+
err = lfs_remove_(lfs, path, lfs_dir_prep_remove_nonempty_folders, true);
6146+
6147+
LFS_TRACE("lfs_remove_recursive -> %d", err);
6148+
LFS_UNLOCK(lfs->cfg);
6149+
return err;
6150+
}
6151+
#endif
6152+
61006153
#ifndef LFS_READONLY
61016154
int lfs_atomic_remove(lfs_t *lfs, const char *path) {
61026155
int err = LFS_LOCK(lfs->cfg);
@@ -6107,7 +6160,7 @@ int lfs_atomic_remove(lfs_t *lfs, const char *path) {
61076160

61086161
// Note: We pass in a helper pointer here so that this extra
61096162
// logic can be dropped if it is never referenced
6110-
err = lfs_remove_(lfs, path, lfs_dir_prep_remove_nonempty_folders);
6163+
err = lfs_remove_(lfs, path, lfs_dir_prep_remove_nonempty_folders, false);
61116164

61126165
LFS_TRACE("lfs_atomic_remove -> %d", err);
61136166
LFS_UNLOCK(lfs->cfg);

lfs.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,18 @@ int lfs_unmount(lfs_t *lfs);
509509
int lfs_remove(lfs_t *lfs, const char *path);
510510
#endif
511511

512+
#ifndef LFS_READONLY
513+
// Removes a file or directory recursively
514+
//
515+
// Note: If power is interrupted, you might end up with
516+
// a partially emptied folder. This routine removes
517+
// a leaf at a time atomically and will eventually
518+
// remove the only leaf left (the top leaf).
519+
//
520+
// Returns a negative error code on failure.
521+
int lfs_remove_recursive(lfs_t *lfs, const char *path);
522+
#endif
523+
512524
#ifndef LFS_READONLY
513525
// Removes a file or directory
514526
//

tests/test_remove_recursive.toml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
[cases.recursive_remove]
2+
defines.N = [1, 3, 10, 100]
3+
defines.M = [1, 3, 10, 100]
4+
if = 'M * N < BLOCK_COUNT/2'
5+
code = '''
6+
lfs_t lfs;
7+
char path[1024];
8+
int i, j;
9+
lfs_format(&lfs, cfg) => 0;
10+
lfs_mount(&lfs, cfg) => 0;
11+
lfs_ssize_t fs_size = lfs_fs_size(&lfs);
12+
lfs_mkdir(&lfs, "prickly-pear") => 0;
13+
srand(1);
14+
15+
for (i = 0; i < N; i++) {
16+
sprintf(path, "prickly-pear/cactus%03d", i);
17+
lfs_mkdir(&lfs, path) => 0;
18+
}
19+
20+
// For the first 5 directories, insert child files
21+
for (i = 0; i < 5 && i < N; i++) {
22+
lfs_file_t file;
23+
for (j = 0; j < M; j++) {
24+
sprintf(path, "prickly-pear/cactus%03d/%03d", i, j);
25+
lfs_file_open(&lfs, &file, path,
26+
LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL) => 0;
27+
lfs_file_truncate(&lfs, &file, 512) => 0;
28+
lfs_file_close(&lfs, &file) => 0;
29+
}
30+
}
31+
32+
lfs_dir_t dir;
33+
struct lfs_info info;
34+
lfs_dir_open(&lfs, &dir, "prickly-pear") => 0;
35+
lfs_dir_read(&lfs, &dir, &info) => 1;
36+
assert(info.type == LFS_TYPE_DIR);
37+
assert(strcmp(info.name, ".") == 0);
38+
lfs_dir_read(&lfs, &dir, &info) => 1;
39+
assert(info.type == LFS_TYPE_DIR);
40+
assert(strcmp(info.name, "..") == 0);
41+
for (i = 0; i < N; i++) {
42+
sprintf(path, "cactus%03d", i);
43+
lfs_dir_read(&lfs, &dir, &info) => 1;
44+
assert(info.type == LFS_TYPE_DIR);
45+
assert(strcmp(info.name, path) == 0);
46+
}
47+
lfs_dir_read(&lfs, &dir, &info) => 0;
48+
lfs_dir_close(&lfs, &dir) => 0;
49+
lfs_unmount(&lfs);
50+
51+
lfs_mount(&lfs, cfg) => 0;
52+
lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOTEMPTY;
53+
lfs_remove_recursive(&lfs, "prickly-pear") => 0;
54+
55+
lfs_fs_size(&lfs) => fs_size;
56+
57+
lfs_remove(&lfs, "prickly-pear/cactus001") => LFS_ERR_NOENT;
58+
59+
lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
60+
lfs_unmount(&lfs) => 0;
61+
62+
63+
lfs_mount(&lfs, cfg) => 0;
64+
lfs_fs_size(&lfs) => fs_size;
65+
lfs_remove(&lfs, "prickly-pear") => LFS_ERR_NOENT;
66+
lfs_unmount(&lfs) => 0;
67+
'''

0 commit comments

Comments
 (0)