mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Collapsed recursive deorphans into a single pass
Because a block can go bad at any time, if we're unlucky, we may end up generating multiple orphans in a single metadata write. This is exacerbated by the early eviction in dynamic wear-leveling. We can't track _all_ orphans, because that would require unbounded storage and significantly complicate things, but there are a handful of intentional orphans we do track because they are easy to resolve without the O(n^2) deorphan scan. These are anytime we intentionally remove a metadata-pair. Initially we cleaned up orphans as they occur with whatever knowledge we do have, and just accepted the extra O(n^2) deorphan scans in the unlucky case. However we can do a bit better by being lazy and leaving deorphaning up to the next metadata write. This needs to work with the known orphans while still setting the orphan flag on disk correctly. To accomplish this we replace the internal flag with a small counter. Note, this means that our internal representation of orphans differs from what's on disk. This is annoying but not the end of the world.
This commit is contained in:
		
							
								
								
									
										125
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -451,31 +451,32 @@ static inline void lfs_global_zero(lfs_global_t *a) { | |||||||
| } | } | ||||||
|  |  | ||||||
| static inline void lfs_global_fromle32(lfs_global_t *a) { | static inline void lfs_global_fromle32(lfs_global_t *a) { | ||||||
|     lfs_pair_fromle32(a->s.movepair); |     lfs_pair_fromle32(a->l.movepair); | ||||||
|     a->s.moveid = lfs_fromle16(a->s.moveid); |     a->l.moveid = lfs_fromle16(a->l.moveid); | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline void lfs_global_tole32(lfs_global_t *a) { | static inline void lfs_global_tole32(lfs_global_t *a) { | ||||||
|     lfs_pair_tole32(a->s.movepair); |     lfs_pair_tole32(a->l.movepair); | ||||||
|     a->s.moveid = lfs_tole16(a->s.moveid); |     a->l.moveid = lfs_tole16(a->l.moveid); | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline void lfs_global_move(lfs_t *lfs, | static inline void lfs_global_move(lfs_t *lfs, | ||||||
|         const lfs_block_t pair[2], uint16_t id) { |         const lfs_block_t pair[2], uint16_t id) { | ||||||
|     lfs_global_t diff; |     lfs_global_t diff; | ||||||
|     lfs_global_zero(&diff); |     lfs_global_zero(&diff); | ||||||
|     diff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ pair[0]; |     diff.l.movepair[0] ^= lfs->globals.g.movepair[0] ^ pair[0]; | ||||||
|     diff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ pair[1]; |     diff.l.movepair[1] ^= lfs->globals.g.movepair[1] ^ pair[1]; | ||||||
|     diff.s.moveid      ^= lfs->globals.s.moveid      ^ id; |     diff.l.moveid      ^= lfs->globals.g.moveid      ^ id; | ||||||
|     lfs_global_fromle32(&lfs->locals); |     lfs_global_fromle32(&lfs->locals); | ||||||
|     lfs_global_xor(&lfs->locals, &diff); |     lfs_global_xor(&lfs->locals, &diff); | ||||||
|     lfs_global_tole32(&lfs->locals); |     lfs_global_tole32(&lfs->locals); | ||||||
|     lfs_global_xor(&lfs->globals, &diff); |     lfs_global_xor(&lfs->globals, &diff); | ||||||
| } | } | ||||||
|  |  | ||||||
| static inline void lfs_global_deorphaned(lfs_t *lfs, bool deorphaned) { | static inline void lfs_global_orphans(lfs_t *lfs, int8_t orphans) { | ||||||
|     lfs->locals.s.deorphaned  ^= lfs->globals.s.deorphaned ^ deorphaned; |     lfs->locals.l.deorphaned ^= (lfs->globals.g.orphans == 0); | ||||||
|     lfs->globals.s.deorphaned ^= lfs->globals.s.deorphaned ^ deorphaned; |     lfs->locals.l.deorphaned ^= (lfs->globals.g.orphans + orphans == 0); | ||||||
|  |     lfs->globals.g.orphans += orphans; | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -688,7 +689,7 @@ static int lfs_commit_globals(lfs_t *lfs, struct lfs_commit *commit, | |||||||
|  |  | ||||||
|     lfs_global_xor(locals, &lfs->locals); |     lfs_global_xor(locals, &lfs->locals); | ||||||
|     int err = lfs_commit_attr(lfs, commit, |     int err = lfs_commit_attr(lfs, commit, | ||||||
|             LFS_MKTAG(LFS_TYPE_GLOBALS + locals->s.deorphaned, 0x3ff, 10), |             LFS_MKTAG(LFS_TYPE_GLOBALS + locals->l.deorphaned, 0x3ff, 10), | ||||||
|             locals); |             locals); | ||||||
|     lfs_global_xor(locals, &lfs->locals); |     lfs_global_xor(locals, &lfs->locals); | ||||||
|     return err; |     return err; | ||||||
| @@ -907,7 +908,7 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs, | |||||||
|                     } |                     } | ||||||
|                     lfs_pair_fromle32(temptail); |                     lfs_pair_fromle32(temptail); | ||||||
|                 } else if (lfs_tag_subtype(tag) == LFS_TYPE_GLOBALS) { |                 } else if (lfs_tag_subtype(tag) == LFS_TYPE_GLOBALS) { | ||||||
|                     templocals.s.deorphaned = (lfs_tag_type(tag) & 1); |                     templocals.l.deorphaned = (lfs_tag_type(tag) & 1); | ||||||
|                     err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag), |                     err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag), | ||||||
|                             &templocals, 10); |                             &templocals, 10); | ||||||
|                     if (err) { |                     if (err) { | ||||||
| @@ -973,11 +974,11 @@ static int32_t lfs_dir_fetchmatch(lfs_t *lfs, | |||||||
|         // consider what we have good enough |         // consider what we have good enough | ||||||
|         if (dir->off > 0) { |         if (dir->off > 0) { | ||||||
|             // synthetic move |             // synthetic move | ||||||
|             if (lfs_pair_cmp(dir->pair, lfs->globals.s.movepair) == 0) { |             if (lfs_pair_cmp(dir->pair, lfs->globals.g.movepair) == 0) { | ||||||
|                 if (lfs->globals.s.moveid == lfs_tag_id(foundtag)) { |                 if (lfs->globals.g.moveid == lfs_tag_id(foundtag)) { | ||||||
|                     foundtag = LFS_ERR_NOENT; |                     foundtag = LFS_ERR_NOENT; | ||||||
|                 } else if (lfs_tag_isvalid(foundtag) && |                 } else if (lfs_tag_isvalid(foundtag) && | ||||||
|                         lfs->globals.s.moveid < lfs_tag_id(foundtag)) { |                         lfs->globals.g.moveid < lfs_tag_id(foundtag)) { | ||||||
|                     foundtag -= LFS_MKTAG(0, 1, 0); |                     foundtag -= LFS_MKTAG(0, 1, 0); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -1026,8 +1027,8 @@ static int32_t lfs_dir_find(lfs_t *lfs, | |||||||
| static int32_t lfs_dir_get(lfs_t *lfs, lfs_mdir_t *dir, | static int32_t lfs_dir_get(lfs_t *lfs, lfs_mdir_t *dir, | ||||||
|         uint32_t getmask, uint32_t gettag, void *buffer) { |         uint32_t getmask, uint32_t gettag, void *buffer) { | ||||||
|     int32_t getdiff = 0; |     int32_t getdiff = 0; | ||||||
|     if (lfs_pair_cmp(dir->pair, lfs->globals.s.movepair) == 0 && |     if (lfs_pair_cmp(dir->pair, lfs->globals.g.movepair) == 0 && | ||||||
|             lfs_tag_id(gettag) <= lfs->globals.s.moveid) { |             lfs_tag_id(gettag) <= lfs->globals.g.moveid) { | ||||||
|         // synthetic moves |         // synthetic moves | ||||||
|         getdiff = LFS_MKTAG(0, 1, 0); |         getdiff = LFS_MKTAG(0, 1, 0); | ||||||
|     } |     } | ||||||
| @@ -1270,17 +1271,17 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | |||||||
|     lfs_mattr_t cancelattr; |     lfs_mattr_t cancelattr; | ||||||
|     lfs_global_t canceldiff; |     lfs_global_t canceldiff; | ||||||
|     lfs_global_zero(&canceldiff); |     lfs_global_zero(&canceldiff); | ||||||
|     if (lfs_pair_cmp(dir->pair, lfs->globals.s.movepair) == 0) { |     if (lfs_pair_cmp(dir->pair, lfs->globals.g.movepair) == 0) { | ||||||
|         // Wait, we have the move? Just cancel this out here |         // Wait, we have the move? Just cancel this out here | ||||||
|         // We need to, or else the move can become outdated |         // We need to, or else the move can become outdated | ||||||
|         canceldiff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ 0xffffffff; |         canceldiff.l.movepair[0] ^= lfs->globals.g.movepair[0] ^ 0xffffffff; | ||||||
|         canceldiff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ 0xffffffff; |         canceldiff.l.movepair[1] ^= lfs->globals.g.movepair[1] ^ 0xffffffff; | ||||||
|         canceldiff.s.moveid      ^= lfs->globals.s.moveid      ^ 0x3ff; |         canceldiff.l.moveid      ^= lfs->globals.g.moveid      ^ 0x3ff; | ||||||
|         lfs_global_fromle32(&lfs->locals); |         lfs_global_fromle32(&lfs->locals); | ||||||
|         lfs_global_xor(&lfs->locals, &canceldiff); |         lfs_global_xor(&lfs->locals, &canceldiff); | ||||||
|         lfs_global_tole32(&lfs->locals); |         lfs_global_tole32(&lfs->locals); | ||||||
|  |  | ||||||
|         cancelattr.tag = LFS_MKTAG(LFS_TYPE_DELETE, lfs->globals.s.moveid, 0); |         cancelattr.tag = LFS_MKTAG(LFS_TYPE_DELETE, lfs->globals.l.moveid, 0); | ||||||
|         cancelattr.next = attrs; |         cancelattr.next = attrs; | ||||||
|         attrs = &cancelattr; |         attrs = &cancelattr; | ||||||
|     } |     } | ||||||
| @@ -2636,7 +2637,7 @@ int lfs_remove(lfs_t *lfs, const char *path) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // mark fs as orphaned |         // mark fs as orphaned | ||||||
|         lfs_global_deorphaned(lfs, false); |         lfs_global_orphans(lfs, +1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // delete the entry |     // delete the entry | ||||||
| @@ -2648,14 +2649,14 @@ int lfs_remove(lfs_t *lfs, const char *path) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (lfs_tag_type(tag) == LFS_TYPE_DIR) { |     if (lfs_tag_type(tag) == LFS_TYPE_DIR) { | ||||||
|  |         // fix orphan | ||||||
|  |         lfs_global_orphans(lfs, -1); | ||||||
|  |  | ||||||
|         err = lfs_fs_pred(lfs, dir.pair, &cwd); |         err = lfs_fs_pred(lfs, dir.pair, &cwd); | ||||||
|         if (err) { |         if (err) { | ||||||
|             return err; |             return err; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // fix orphan |  | ||||||
|         lfs_global_deorphaned(lfs, true); |  | ||||||
|  |  | ||||||
|         // steal state |         // steal state | ||||||
|         cwd.tail[0] = dir.tail[0]; |         cwd.tail[0] = dir.tail[0]; | ||||||
|         cwd.tail[1] = dir.tail[1]; |         cwd.tail[1] = dir.tail[1]; | ||||||
| @@ -2696,13 +2697,18 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { | |||||||
|     uint16_t newid = lfs_tag_id(prevtag); |     uint16_t newid = lfs_tag_id(prevtag); | ||||||
|  |  | ||||||
|     lfs_mdir_t prevdir; |     lfs_mdir_t prevdir; | ||||||
|     if (prevtag != LFS_ERR_NOENT) { |     if (prevtag == LFS_ERR_NOENT) { | ||||||
|         // check that we have same type |         // check that name fits | ||||||
|         if (lfs_tag_type(prevtag) != lfs_tag_type(oldtag)) { |         lfs_size_t nlen = strlen(newpath); | ||||||
|             return LFS_ERR_ISDIR; |         if (nlen > lfs->name_max) { | ||||||
|  |             return LFS_ERR_NAMETOOLONG; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (lfs_tag_type(prevtag) == LFS_TYPE_DIR) { |         // get next id | ||||||
|  |         newid = newcwd.count; | ||||||
|  |     } else if (lfs_tag_type(prevtag) != lfs_tag_type(oldtag)) { | ||||||
|  |         return LFS_ERR_ISDIR; | ||||||
|  |     } else if (lfs_tag_type(prevtag) == LFS_TYPE_DIR) { | ||||||
|         // must be empty before removal |         // must be empty before removal | ||||||
|         lfs_block_t prevpair[2]; |         lfs_block_t prevpair[2]; | ||||||
|         int32_t res = lfs_dir_get(lfs, &newcwd, 0x7c3ff000, |         int32_t res = lfs_dir_get(lfs, &newcwd, 0x7c3ff000, | ||||||
| @@ -2723,17 +2729,7 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // mark fs as orphaned |         // mark fs as orphaned | ||||||
|             lfs_global_deorphaned(lfs, false); |         lfs_global_orphans(lfs, +1); | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         // check that name fits |  | ||||||
|         lfs_size_t nlen = strlen(newpath); |  | ||||||
|         if (nlen > lfs->name_max) { |  | ||||||
|             return LFS_ERR_NAMETOOLONG; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // get next id |  | ||||||
|         newid = newcwd.count; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // create move to fix later |     // create move to fix later | ||||||
| @@ -2758,14 +2754,14 @@ int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (prevtag != LFS_ERR_NOENT && lfs_tag_type(prevtag) == LFS_TYPE_DIR) { |     if (prevtag != LFS_ERR_NOENT && lfs_tag_type(prevtag) == LFS_TYPE_DIR) { | ||||||
|  |         // fix orphan | ||||||
|  |         lfs_global_orphans(lfs, -1); | ||||||
|  |  | ||||||
|         err = lfs_fs_pred(lfs, prevdir.pair, &newcwd); |         err = lfs_fs_pred(lfs, prevdir.pair, &newcwd); | ||||||
|         if (err) { |         if (err) { | ||||||
|             return err; |             return err; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // fix orphan |  | ||||||
|         lfs_global_deorphaned(lfs, true); |  | ||||||
|  |  | ||||||
|         // steal state |         // steal state | ||||||
|         newcwd.tail[0] = prevdir.tail[0]; |         newcwd.tail[0] = prevdir.tail[0]; | ||||||
|         newcwd.tail[1] = prevdir.tail[1]; |         newcwd.tail[1] = prevdir.tail[1]; | ||||||
| @@ -2917,10 +2913,10 @@ static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) { | |||||||
|     lfs->root[1] = 0xffffffff; |     lfs->root[1] = 0xffffffff; | ||||||
|     lfs->mlist = NULL; |     lfs->mlist = NULL; | ||||||
|     lfs->seed = 0; |     lfs->seed = 0; | ||||||
|     lfs->globals.s.movepair[0] = 0xffffffff; |     lfs->globals.g.movepair[0] = 0xffffffff; | ||||||
|     lfs->globals.s.movepair[1] = 0xffffffff; |     lfs->globals.g.movepair[1] = 0xffffffff; | ||||||
|     lfs->globals.s.moveid = 0x3ff; |     lfs->globals.g.moveid = 0x3ff; | ||||||
|     lfs->globals.s.deorphaned = true; |     lfs->globals.g.orphans = 0; | ||||||
|     lfs_global_zero(&lfs->locals); |     lfs_global_zero(&lfs->locals); | ||||||
|  |  | ||||||
|     return 0; |     return 0; | ||||||
| @@ -3089,11 +3085,11 @@ int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) { | |||||||
|     lfs_global_fromle32(&lfs->locals); |     lfs_global_fromle32(&lfs->locals); | ||||||
|     lfs_global_xor(&lfs->globals, &lfs->locals); |     lfs_global_xor(&lfs->globals, &lfs->locals); | ||||||
|     lfs_global_zero(&lfs->locals); |     lfs_global_zero(&lfs->locals); | ||||||
|     if (!lfs_pair_isnull(lfs->globals.s.movepair)) { |     if (!lfs_pair_isnull(lfs->globals.g.movepair)) { | ||||||
|         LFS_DEBUG("Found move %"PRIu32" %"PRIu32" %"PRIu32, |         LFS_DEBUG("Found move %"PRIu32" %"PRIu32" %"PRIu32, | ||||||
|                 lfs->globals.s.movepair[0], |                 lfs->globals.g.movepair[0], | ||||||
|                 lfs->globals.s.movepair[1], |                 lfs->globals.g.movepair[1], | ||||||
|                 lfs->globals.s.moveid); |                 lfs->globals.g.moveid); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // setup free lookahead |     // setup free lookahead | ||||||
| @@ -3254,7 +3250,8 @@ static int lfs_fs_relocate(lfs_t *lfs, | |||||||
|  |  | ||||||
|     if (tag != LFS_ERR_NOENT) { |     if (tag != LFS_ERR_NOENT) { | ||||||
|         // update disk, this creates a desync |         // update disk, this creates a desync | ||||||
|         lfs_global_deorphaned(lfs, false); |         lfs_global_orphans(lfs, +1); | ||||||
|  |  | ||||||
|         lfs_pair_tole32(newpair); |         lfs_pair_tole32(newpair); | ||||||
|         int err = lfs_dir_commit(lfs, &parent, |         int err = lfs_dir_commit(lfs, &parent, | ||||||
|                 &(lfs_mattr_t){.tag=tag, .buffer=newpair}); |                 &(lfs_mattr_t){.tag=tag, .buffer=newpair}); | ||||||
| @@ -3263,8 +3260,8 @@ static int lfs_fs_relocate(lfs_t *lfs, | |||||||
|             return err; |             return err; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // clean up bad block, which should now be a desync |         // next step, clean up orphans | ||||||
|         return lfs_fs_deorphan(lfs); |         lfs_global_orphans(lfs, -1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // find pred |     // find pred | ||||||
| @@ -3275,7 +3272,7 @@ static int lfs_fs_relocate(lfs_t *lfs, | |||||||
|  |  | ||||||
|     // if we can't find dir, it must be new |     // if we can't find dir, it must be new | ||||||
|     if (err != LFS_ERR_NOENT) { |     if (err != LFS_ERR_NOENT) { | ||||||
|         // just replace bad pair, no desync can occur |         // replace bad pair, either we clean up desync, or no desync occured | ||||||
|         parent.tail[0] = newpair[0]; |         parent.tail[0] = newpair[0]; | ||||||
|         parent.tail[1] = newpair[1]; |         parent.tail[1] = newpair[1]; | ||||||
|         err = lfs_dir_commit(lfs, &parent, |         err = lfs_dir_commit(lfs, &parent, | ||||||
| @@ -3359,20 +3356,20 @@ static int lfs_fs_deorphan(lfs_t *lfs) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // mark orphans as fixed |     // mark orphans as fixed | ||||||
|     lfs_global_deorphaned(lfs, true); |     lfs_global_orphans(lfs, -lfs->globals.g.orphans); | ||||||
|     return 0; |     return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int lfs_fs_demove(lfs_t *lfs) { | static int lfs_fs_demove(lfs_t *lfs) { | ||||||
|     // Fix bad moves |     // Fix bad moves | ||||||
|     LFS_DEBUG("Fixing move %"PRIu32" %"PRIu32" %"PRIu32, |     LFS_DEBUG("Fixing move %"PRIu32" %"PRIu32" %"PRIu32, | ||||||
|             lfs->globals.s.movepair[0], |             lfs->globals.g.movepair[0], | ||||||
|             lfs->globals.s.movepair[1], |             lfs->globals.g.movepair[1], | ||||||
|             lfs->globals.s.moveid); |             lfs->globals.g.moveid); | ||||||
|  |  | ||||||
|     // fetch and delete the moved entry |     // fetch and delete the moved entry | ||||||
|     lfs_mdir_t movedir; |     lfs_mdir_t movedir; | ||||||
|     int err = lfs_dir_fetch(lfs, &movedir, lfs->globals.s.movepair); |     int err = lfs_dir_fetch(lfs, &movedir, lfs->globals.g.movepair); | ||||||
|     if (err) { |     if (err) { | ||||||
|         return err; |         return err; | ||||||
|     } |     } | ||||||
| @@ -3387,14 +3384,14 @@ static int lfs_fs_demove(lfs_t *lfs) { | |||||||
| } | } | ||||||
|  |  | ||||||
| static int lfs_fs_forceconsistency(lfs_t *lfs) { | static int lfs_fs_forceconsistency(lfs_t *lfs) { | ||||||
|     if (!lfs->globals.s.deorphaned) { |     if (lfs->globals.g.orphans) { | ||||||
|         int err = lfs_fs_deorphan(lfs); |         int err = lfs_fs_deorphan(lfs); | ||||||
|         if (err) { |         if (err) { | ||||||
|             return err; |             return err; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (lfs->globals.s.moveid != 0x3ff) { |     if (lfs->globals.g.moveid != 0x3ff) { | ||||||
|         int err = lfs_fs_demove(lfs); |         int err = lfs_fs_demove(lfs); | ||||||
|         if (err) { |         if (err) { | ||||||
|             return err; |             return err; | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								lfs.h
									
									
									
									
									
								
							| @@ -302,8 +302,13 @@ typedef union lfs_global { | |||||||
|     struct { |     struct { | ||||||
|         lfs_block_t movepair[2]; |         lfs_block_t movepair[2]; | ||||||
|         uint16_t moveid; |         uint16_t moveid; | ||||||
|         bool deorphaned; |         uint8_t deorphaned; | ||||||
|     } s; |     } l; | ||||||
|  |     struct { | ||||||
|  |         lfs_block_t movepair[2]; | ||||||
|  |         uint16_t moveid; | ||||||
|  |         uint8_t orphans; | ||||||
|  |     } g; | ||||||
| } lfs_global_t; | } lfs_global_t; | ||||||
|  |  | ||||||
| typedef struct lfs_mdir { | typedef struct lfs_mdir { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user