Skip to content

Commit dd12a7a

Browse files
Support removal of non-empty folders via lfs_atomic_*(...)
Introduce the function lfs_atomic_rename(...) and lfs_atomic_remove(...) that permits the removal of a folder with multiple files within it. This fails when the directory contains other directories.
1 parent 850f63c commit dd12a7a

File tree

3 files changed

+193
-9
lines changed

3 files changed

+193
-9
lines changed

lfs.c

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3884,6 +3884,9 @@ struct lfs_remove_state
38843884
lfs_mdir_t cwd;
38853885
lfs_stag_t tag;
38863886

3887+
// Global state accumulations from a split folder
3888+
lfs_gstate_t gstate;
3889+
38873890
// Directory that needs to be removed from linked list pointer
38883891
struct lfs_mlist dir;
38893892

@@ -3892,10 +3895,61 @@ struct lfs_remove_state
38923895
// the directory.
38933896
lfs_block_t dir_head[2];
38943897
};
3898+
typedef int (*lfs_dir_prep_helper_t)(lfs_t *, struct lfs_remove_state *);
38953899
#endif
38963900

38973901
#ifndef LFS_READONLY
3898-
static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p) {
3902+
static int lfs_dir_prep_remove_nonempty_folders(lfs_t *lfs, struct lfs_remove_state *p) {
3903+
uint16_t id = 0;
3904+
3905+
// 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.
3911+
while(true) {
3912+
if (p->dir.m.count == id) {
3913+
if (!p->dir.m.split) {
3914+
// We have iterated through the folder to the last
3915+
// tag.
3916+
break;
3917+
}
3918+
3919+
// Before we fetch the next block, update our view of gstate
3920+
lfs_dir_getgstate(lfs, &p->dir.m, &p->gstate);
3921+
3922+
int err = lfs_dir_fetch(lfs, &p->dir.m, p->dir.m.tail);
3923+
if (err) {
3924+
return err;
3925+
}
3926+
3927+
id = 0;
3928+
}
3929+
3930+
char name[lfs->name_max + 1];
3931+
lfs_stag_t tag = lfs_dir_get(lfs, &p->dir.m, LFS_MKTAG(0x780, 0x3ff, 0),
3932+
LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_max + 1), name);
3933+
if (tag < 0) {
3934+
return tag;
3935+
}
3936+
3937+
if (lfs_tag_type3(tag) == LFS_TYPE_DIR) {
3938+
// We're not allowed to find leafs
3939+
return LFS_ERR_NOTEMPTY;
3940+
}
3941+
3942+
id += 1;
3943+
}
3944+
3945+
return 0;
3946+
}
3947+
#endif
3948+
3949+
#ifndef LFS_READONLY
3950+
static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p,
3951+
lfs_dir_prep_helper_t helper) {
3952+
38993953
lfs_stag_t res = lfs_dir_get(lfs, &p->cwd, LFS_MKTAG(0x700, 0x3ff, 0),
39003954
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tag_id(p->tag), 8), p->dir_head);
39013955
if (res < 0) {
@@ -3908,8 +3962,19 @@ static int lfs_dir_prep_removal(lfs_t *lfs, struct lfs_remove_state *p) {
39083962
return err;
39093963
}
39103964

3965+
memset(&p->gstate, 0, sizeof(p->gstate));
3966+
39113967
if (p->dir.m.count > 0 || p->dir.m.split) {
3912-
return LFS_ERR_NOTEMPTY;
3968+
// Normal POSIX behavior wouldn't allow a non-empty
3969+
// folder to be removed/renamed into in this manner
3970+
if (NULL == helper) {
3971+
return LFS_ERR_NOTEMPTY;
3972+
}
3973+
3974+
err = helper(lfs, p);
3975+
if (err) {
3976+
return err;
3977+
}
39133978
}
39143979

39153980
// mark fs as orphaned
@@ -3937,11 +4002,15 @@ static int lfs_dir_finish_removal(lfs_t *lfs, struct lfs_remove_state *p)
39374002
return err;
39384003
}
39394004

3940-
err = lfs_fs_pred(lfs, p->dir.m.pair, &p->cwd);
4005+
err = lfs_fs_pred(lfs, p->dir_head, &p->cwd);
39414006
if (err) {
39424007
return err;
39434008
}
39444009

4010+
// Merge in gstate from first block splits within the directory;
4011+
// lfs_dir_drop will pick up the last gstate entry.
4012+
lfs_gstate_xor(&lfs->gdelta, &p->gstate);
4013+
39454014
err = lfs_dir_drop(lfs, &p->cwd, &p->dir.m);
39464015
if (err) {
39474016
return err;
@@ -3952,7 +4021,7 @@ static int lfs_dir_finish_removal(lfs_t *lfs, struct lfs_remove_state *p)
39524021
#endif
39534022

39544023
#ifndef LFS_READONLY
3955-
static int lfs_remove_(lfs_t *lfs, const char *path) {
4024+
static int lfs_remove_(lfs_t *lfs, const char *path, lfs_dir_prep_helper_t helper) {
39564025
// deorphan if we haven't yet, needed at most once after poweron
39574026
int err = lfs_fs_forceconsistency(lfs);
39584027
if (err) {
@@ -3967,7 +4036,7 @@ static int lfs_remove_(lfs_t *lfs, const char *path) {
39674036

39684037
s.dir.next = lfs->mlist;
39694038
if (lfs_tag_type3(s.tag) == LFS_TYPE_DIR) {
3970-
err = lfs_dir_prep_removal(lfs, &s);
4039+
err = lfs_dir_prep_removal(lfs, &s, helper);
39714040
if (err < 0) {
39724041
return err;
39734042
}
@@ -3994,7 +4063,8 @@ static int lfs_remove_(lfs_t *lfs, const char *path) {
39944063
#endif
39954064

39964065
#ifndef LFS_READONLY
3997-
static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
4066+
static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath,
4067+
lfs_dir_prep_helper_t helper) {
39984068
// deorphan if we haven't yet, needed at most once after poweron
39994069
int err = lfs_fs_forceconsistency(lfs);
40004070
if (err) {
@@ -4049,7 +4119,7 @@ static int lfs_rename_(lfs_t *lfs, const char *oldpath, const char *newpath) {
40494119
// we're renaming to ourselves??
40504120
return 0;
40514121
} else if (lfs_tag_type3(n.tag) == LFS_TYPE_DIR) {
4052-
err = lfs_dir_prep_removal(lfs, &n);
4122+
err = lfs_dir_prep_removal(lfs, &n, helper);
40534123
if (err) {
40544124
return err;
40554125
}
@@ -6019,14 +6089,32 @@ int lfs_remove(lfs_t *lfs, const char *path) {
60196089
}
60206090
LFS_TRACE("lfs_remove(%p, \"%s\")", (void*)lfs, path);
60216091

6022-
err = lfs_remove_(lfs, path);
6092+
err = lfs_remove_(lfs, path, NULL);
60236093

60246094
LFS_TRACE("lfs_remove -> %d", err);
60256095
LFS_UNLOCK(lfs->cfg);
60266096
return err;
60276097
}
60286098
#endif
60296099

6100+
#ifndef LFS_READONLY
6101+
int lfs_atomic_remove(lfs_t *lfs, const char *path) {
6102+
int err = LFS_LOCK(lfs->cfg);
6103+
if (err) {
6104+
return err;
6105+
}
6106+
LFS_TRACE("lfs_atomic_remove(%p, \"%s\")", (void*)lfs, path);
6107+
6108+
// Note: We pass in a helper pointer here so that this extra
6109+
// logic can be dropped if it is never referenced
6110+
err = lfs_remove_(lfs, path, lfs_dir_prep_remove_nonempty_folders);
6111+
6112+
LFS_TRACE("lfs_atomic_remove -> %d", err);
6113+
LFS_UNLOCK(lfs->cfg);
6114+
return err;
6115+
}
6116+
#endif
6117+
60306118
#ifndef LFS_READONLY
60316119
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
60326120
int err = LFS_LOCK(lfs->cfg);
@@ -6035,14 +6123,32 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
60356123
}
60366124
LFS_TRACE("lfs_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
60376125

6038-
err = lfs_rename_(lfs, oldpath, newpath);
6126+
err = lfs_rename_(lfs, oldpath, newpath, NULL);
60396127

60406128
LFS_TRACE("lfs_rename -> %d", err);
60416129
LFS_UNLOCK(lfs->cfg);
60426130
return err;
60436131
}
60446132
#endif
60456133

6134+
#ifndef LFS_READONLY
6135+
int lfs_atomic_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
6136+
int err = LFS_LOCK(lfs->cfg);
6137+
if (err) {
6138+
return err;
6139+
}
6140+
LFS_TRACE("lfs_atomic_rename(%p, \"%s\", \"%s\")", (void*)lfs, oldpath, newpath);
6141+
6142+
// Note: We pass in a helper pointer here so that this extra
6143+
// logic can be dropped if it is never referenced
6144+
err = lfs_rename_(lfs, oldpath, newpath, lfs_dir_prep_remove_nonempty_folders);
6145+
6146+
LFS_TRACE("lfs_atomic_rename -> %d", err);
6147+
LFS_UNLOCK(lfs->cfg);
6148+
return err;
6149+
}
6150+
#endif
6151+
60466152
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
60476153
int err = LFS_LOCK(lfs->cfg);
60486154
if (err) {

lfs.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,17 @@ 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
514+
//
515+
// If removing a directory, the directory must not have
516+
// any directories but it may contain files. This is
517+
// non-POSIX behavior, and thus is a different call
518+
// than lfs_remove(...)
519+
// Returns a negative error code on failure.
520+
int lfs_atomic_remove(lfs_t *lfs, const char *path);
521+
#endif
522+
512523
#ifndef LFS_READONLY
513524
// Rename or move a file or directory
514525
//
@@ -519,6 +530,19 @@ int lfs_remove(lfs_t *lfs, const char *path);
519530
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
520531
#endif
521532

533+
#ifndef LFS_READONLY
534+
// Rename or move a file or directory
535+
//
536+
// If the destination exists, it must match the source in type.
537+
// If the destination is a directory, it may not contain
538+
// any directories but it may contain files. This is
539+
// non-POSIX behavior, and thus is a different call
540+
// than lfs_rename(...)
541+
//
542+
// Returns a negative error code on failure.
543+
int lfs_atomic_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
544+
#endif
545+
522546
// Find info about a file or directory
523547
//
524548
// Fills out the info structure, based on the specified file or directory.

tests/test_atomics.toml

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

0 commit comments

Comments
 (0)