mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	Compare commits
	
		
			8 Commits
		
	
	
		
			v2.2.1
			...
			test-revam
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | b9e403d55c | ||
|  | d58aaf88dc | ||
|  | 71c844be53 | ||
|  | 75cd51b39e | ||
|  | fc354801fa | ||
|  | 557ec332fe | ||
|  | 5e839df234 | ||
|  | 47ab0426b1 | 
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -7,4 +7,4 @@ | ||||
| blocks/ | ||||
| lfs | ||||
| test.c | ||||
| tests_/*.toml.* | ||||
| tests/*.toml.* | ||||
|   | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -45,7 +45,7 @@ test: | ||||
| 	./scripts/test.py $(TFLAGS) | ||||
| .SECONDEXPANSION: | ||||
| test%: tests/test$$(firstword $$(subst \#, ,%)).toml | ||||
| 	./scripts/test.py $(TFLAGS) $@ | ||||
| 	./scripts/test.py $@ $(TFLAGS) | ||||
|  | ||||
| -include $(DEP) | ||||
|  | ||||
|   | ||||
| @@ -47,7 +47,7 @@ int lfs_testbd_createcfg(const struct lfs_config *cfg, const char *path, | ||||
|  | ||||
|         memset(bd->wear, 0, sizeof(lfs_testbd_wear_t) * cfg->block_count); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // create underlying block device | ||||
|     if (bd->persist) { | ||||
|         bd->u.file.cfg = (struct lfs_filebd_config){ | ||||
| @@ -155,9 +155,8 @@ int lfs_testbd_read(const struct lfs_config *cfg, lfs_block_t block, | ||||
|     LFS_ASSERT(block < cfg->block_count); | ||||
|  | ||||
|     // block bad? | ||||
|     if (bd->cfg->erase_cycles && | ||||
|             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOREAD && | ||||
|             bd->wear[block] >= bd->cfg->erase_cycles) { | ||||
|     if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles && | ||||
|             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_READERROR) { | ||||
|         LFS_TRACE("lfs_testbd_read -> %d", LFS_ERR_CORRUPT); | ||||
|         return LFS_ERR_CORRUPT; | ||||
|     } | ||||
| @@ -180,11 +179,18 @@ int lfs_testbd_prog(const struct lfs_config *cfg, lfs_block_t block, | ||||
|     LFS_ASSERT(block < cfg->block_count); | ||||
|  | ||||
|     // block bad? | ||||
|     if (bd->cfg->erase_cycles && | ||||
|             bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOPROG && | ||||
|             bd->wear[block] >= bd->cfg->erase_cycles) { | ||||
|         LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); | ||||
|         return LFS_ERR_CORRUPT; | ||||
|     if (bd->cfg->erase_cycles && bd->wear[block] >= bd->cfg->erase_cycles) { | ||||
|         if (bd->cfg->badblock_behavior == | ||||
|                 LFS_TESTBD_BADBLOCK_PROGERROR) { | ||||
|             LFS_TRACE("lfs_testbd_prog -> %d", LFS_ERR_CORRUPT); | ||||
|             return LFS_ERR_CORRUPT; | ||||
|         } else if (bd->cfg->badblock_behavior == | ||||
|                 LFS_TESTBD_BADBLOCK_PROGNOOP || | ||||
|                 bd->cfg->badblock_behavior == | ||||
|                 LFS_TESTBD_BADBLOCK_ERASENOOP) { | ||||
|             LFS_TRACE("lfs_testbd_prog -> %d", 0); | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // prog | ||||
| @@ -219,9 +225,14 @@ int lfs_testbd_erase(const struct lfs_config *cfg, lfs_block_t block) { | ||||
|     // block bad? | ||||
|     if (bd->cfg->erase_cycles) { | ||||
|         if (bd->wear[block] >= bd->cfg->erase_cycles) { | ||||
|             if (bd->cfg->badblock_behavior == LFS_TESTBD_BADBLOCK_NOERASE) { | ||||
|             if (bd->cfg->badblock_behavior == | ||||
|                     LFS_TESTBD_BADBLOCK_ERASEERROR) { | ||||
|                 LFS_TRACE("lfs_testbd_erase -> %d", LFS_ERR_CORRUPT); | ||||
|                 return LFS_ERR_CORRUPT; | ||||
|             } else if (bd->cfg->badblock_behavior == | ||||
|                     LFS_TESTBD_BADBLOCK_ERASENOOP) { | ||||
|                 LFS_TRACE("lfs_testbd_erase -> %d", 0); | ||||
|                 return 0; | ||||
|             } | ||||
|         } else { | ||||
|             // mark wear | ||||
|   | ||||
| @@ -19,14 +19,18 @@ extern "C" | ||||
| #endif | ||||
|  | ||||
|  | ||||
| // Mode determining how "bad blocks" behave during testing. This | ||||
| // simulates some real-world circumstances such as writes not | ||||
| // going through (noprog), erases not sticking (noerase), and ECC | ||||
| // failures (noread). | ||||
| // Mode determining how "bad blocks" behave during testing. This simulates | ||||
| // some real-world circumstances such as progs not sticking (prog-noop), | ||||
| // a readonly disk (erase-noop), and ECC failures (read-error). | ||||
| // | ||||
| // Not that read-noop is not allowed. Read _must_ return a consistent (but | ||||
| // may be arbitrary) value on every read. | ||||
| enum lfs_testbd_badblock_behavior { | ||||
|     LFS_TESTBD_BADBLOCK_NOPROG  = 0, | ||||
|     LFS_TESTBD_BADBLOCK_NOERASE = 1, | ||||
|     LFS_TESTBD_BADBLOCK_NOREAD  = 2, | ||||
|     LFS_TESTBD_BADBLOCK_PROGERROR, | ||||
|     LFS_TESTBD_BADBLOCK_ERASEERROR, | ||||
|     LFS_TESTBD_BADBLOCK_READERROR, | ||||
|     LFS_TESTBD_BADBLOCK_PROGNOOP, | ||||
|     LFS_TESTBD_BADBLOCK_ERASENOOP, | ||||
| }; | ||||
|  | ||||
| // Type for measuring wear | ||||
| @@ -82,7 +86,7 @@ typedef struct lfs_testbd { | ||||
| /// Block device API /// | ||||
|  | ||||
| // Create a test block device using the geometry in lfs_config | ||||
| //  | ||||
| // | ||||
| // Note that filebd is used if a path is provided, if path is NULL | ||||
| // testbd will use rambd which can be much faster. | ||||
| int lfs_testbd_create(const struct lfs_config *cfg, const char *path); | ||||
|   | ||||
							
								
								
									
										235
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										235
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -414,6 +414,9 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2], | ||||
|         lfs_mdir_t *parent); | ||||
| static int lfs_fs_relocate(lfs_t *lfs, | ||||
|         const lfs_block_t oldpair[2], lfs_block_t newpair[2]); | ||||
| int lfs_fs_traverseraw(lfs_t *lfs, | ||||
|         int (*cb)(void *data, lfs_block_t block), void *data, | ||||
|         bool includeorphans); | ||||
| static int lfs_fs_forceconsistency(lfs_t *lfs); | ||||
| static int lfs_deinit(lfs_t *lfs); | ||||
| #ifdef LFS_MIGRATE | ||||
| @@ -472,7 +475,7 @@ static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) { | ||||
|  | ||||
|         // find mask of free blocks from tree | ||||
|         memset(lfs->free.buffer, 0, lfs->cfg->lookahead_size); | ||||
|         int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs); | ||||
|         int err = lfs_fs_traverseraw(lfs, lfs_alloc_lookahead, lfs, true); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
| @@ -802,11 +805,13 @@ static lfs_stag_t lfs_dir_fetchmatch(lfs_t *lfs, | ||||
|             tag = lfs_frombe32(tag) ^ ptag; | ||||
|  | ||||
|             // next commit not yet programmed or we're not in valid range | ||||
|             if (!lfs_tag_isvalid(tag) || | ||||
|                     off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { | ||||
|             if (!lfs_tag_isvalid(tag)) { | ||||
|                 dir->erased = (lfs_tag_type1(ptag) == LFS_TYPE_CRC && | ||||
|                         dir->off % lfs->cfg->prog_size == 0); | ||||
|                 break; | ||||
|             } else if (off + lfs_tag_dsize(tag) > lfs->cfg->block_size) { | ||||
|                 dir->erased = false; | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             ptag = tag; | ||||
| @@ -1230,6 +1235,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { | ||||
|     const lfs_off_t off1 = commit->off + sizeof(lfs_tag_t); | ||||
|     const lfs_off_t end = lfs_alignup(off1 + sizeof(uint32_t), | ||||
|             lfs->cfg->prog_size); | ||||
|     uint32_t ncrc = commit->crc; | ||||
|  | ||||
|     // create crc tags to fill up remainder of commit, note that | ||||
|     // padding is not crcd, which lets fetches skip padding but | ||||
| @@ -1266,6 +1272,7 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         ncrc = commit->crc; | ||||
|         commit->off += sizeof(tag)+lfs_tag_size(tag); | ||||
|         commit->ptag = tag ^ ((lfs_tag_t)reset << 31); | ||||
|         commit->crc = LFS_BLOCK_NULL; // reset crc for next "commit" | ||||
| @@ -1292,6 +1299,12 @@ static int lfs_dir_commitcrc(lfs_t *lfs, struct lfs_commit *commit) { | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             // check against written crc to detect if block is readonly | ||||
|             // (we may pick up old commits) | ||||
|             if (i == noff && crc != ncrc) { | ||||
|                 return LFS_ERR_CORRUPT; | ||||
|             } | ||||
|  | ||||
|             crc = lfs_crc(crc, &dat, 1); | ||||
|         } | ||||
|  | ||||
| @@ -1320,6 +1333,9 @@ static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // zero for reproducability in case initial block is unreadable | ||||
|     dir->rev = 0; | ||||
|  | ||||
|     // rather than clobbering one of the blocks we just pretend | ||||
|     // the revision may be valid | ||||
|     int err = lfs_bd_read(lfs, | ||||
| @@ -1420,9 +1436,9 @@ static int lfs_dir_compact(lfs_t *lfs, | ||||
|         lfs_mdir_t *dir, const struct lfs_mattr *attrs, int attrcount, | ||||
|         lfs_mdir_t *source, uint16_t begin, uint16_t end) { | ||||
|     // save some state in case block is bad | ||||
|     const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; | ||||
|     const lfs_block_t oldpair[2] = {dir->pair[0], dir->pair[1]}; | ||||
|     bool relocated = false; | ||||
|     bool exhausted = false; | ||||
|     bool tired = false; | ||||
|  | ||||
|     // should we split? | ||||
|     while (end - begin > 1) { | ||||
| @@ -1469,8 +1485,14 @@ static int lfs_dir_compact(lfs_t *lfs, | ||||
|  | ||||
|     // increment revision count | ||||
|     dir->rev += 1; | ||||
|     // If our revision count == n * block_cycles, we should force a relocation, | ||||
|     // this is how littlefs wear-levels at the metadata-pair level. Note that we | ||||
|     // actually use (block_cycles+1)|1, this is to avoid two corner cases: | ||||
|     // 1. block_cycles = 1, which would prevent relocations from terminating | ||||
|     // 2. block_cycles = 2n, which, due to aliasing, would only ever relocate | ||||
|     //    one metadata block in the pair, effectively making this useless | ||||
|     if (lfs->cfg->block_cycles > 0 && | ||||
|             (dir->rev % (lfs->cfg->block_cycles+1) == 0)) { | ||||
|             (dir->rev % ((lfs->cfg->block_cycles+1)|1) == 0)) { | ||||
|         if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { | ||||
|             // oh no! we're writing too much to the superblock, | ||||
|             // should we expand? | ||||
| @@ -1506,7 +1528,7 @@ static int lfs_dir_compact(lfs_t *lfs, | ||||
| #endif | ||||
|         } else { | ||||
|             // we're writing too much, time to relocate | ||||
|             exhausted = true; | ||||
|             tired = true; | ||||
|             goto relocate; | ||||
|         } | ||||
|     } | ||||
| @@ -1630,23 +1652,23 @@ relocate: | ||||
|         // commit was corrupted, drop caches and prepare to relocate block | ||||
|         relocated = true; | ||||
|         lfs_cache_drop(lfs, &lfs->pcache); | ||||
|         if (!exhausted) { | ||||
|         if (!tired) { | ||||
|             LFS_DEBUG("Bad block at %"PRIx32, dir->pair[1]); | ||||
|         } | ||||
|  | ||||
|         // can't relocate superblock, filesystem is now frozen | ||||
|         if (lfs_pair_cmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { | ||||
|             LFS_WARN("Superblock %"PRIx32" has become unwritable", oldpair[1]); | ||||
|         if (lfs_pair_cmp(dir->pair, (const lfs_block_t[2]){0, 1}) == 0) { | ||||
|             LFS_WARN("Superblock %"PRIx32" has become unwritable", dir->pair[1]); | ||||
|             return LFS_ERR_NOSPC; | ||||
|         } | ||||
|  | ||||
|         // relocate half of pair | ||||
|         int err = lfs_alloc(lfs, &dir->pair[1]); | ||||
|         if (err && (err != LFS_ERR_NOSPC || !exhausted)) { | ||||
|         if (err && (err != LFS_ERR_NOSPC || !tired)) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         exhausted = false; | ||||
|         tired = false; | ||||
|         continue; | ||||
|     } | ||||
|  | ||||
| @@ -1684,6 +1706,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|     } | ||||
|  | ||||
|     // calculate changes to the directory | ||||
|     lfs_mdir_t olddir = *dir; | ||||
|     bool hasdelete = false; | ||||
|     for (int i = 0; i < attrcount; i++) { | ||||
|         if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_CREATE) { | ||||
| @@ -1705,11 +1728,16 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|         lfs_mdir_t pdir; | ||||
|         int err = lfs_fs_pred(lfs, dir->pair, &pdir); | ||||
|         if (err && err != LFS_ERR_NOENT) { | ||||
|             *dir = olddir; | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         if (err != LFS_ERR_NOENT && pdir.split) { | ||||
|             return lfs_dir_drop(lfs, &pdir, dir); | ||||
|             err = lfs_dir_drop(lfs, &pdir, dir); | ||||
|             if (err) { | ||||
|                 *dir = olddir; | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -1737,6 +1765,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|             if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { | ||||
|                 goto compact; | ||||
|             } | ||||
|             *dir = olddir; | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
| @@ -1749,6 +1778,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|         if (!lfs_gstate_iszero(&delta)) { | ||||
|             err = lfs_dir_getgstate(lfs, dir, &delta); | ||||
|             if (err) { | ||||
|                 *dir = olddir; | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
| @@ -1760,6 +1790,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|                 if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { | ||||
|                     goto compact; | ||||
|                 } | ||||
|                 *dir = olddir; | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
| @@ -1770,6 +1801,7 @@ static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir, | ||||
|             if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { | ||||
|                 goto compact; | ||||
|             } | ||||
|             *dir = olddir; | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
| @@ -1788,6 +1820,7 @@ compact: | ||||
|         int err = lfs_dir_compact(lfs, dir, attrs, attrcount, | ||||
|                 dir, 0, dir->count); | ||||
|         if (err) { | ||||
|             *dir = olddir; | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
| @@ -1800,9 +1833,8 @@ compact: | ||||
|     // we need to copy the pair so they don't get clobbered if we refetch | ||||
|     // our mdir. | ||||
|     for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { | ||||
|         if (&d->m != dir && lfs_pair_cmp(d->m.pair, dir->pair) == 0) { | ||||
|         if (&d->m != dir && lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { | ||||
|             d->m = *dir; | ||||
|  | ||||
|             for (int i = 0; i < attrcount; i++) { | ||||
|                 if (lfs_tag_type3(attrs[i].tag) == LFS_TYPE_DELETE && | ||||
|                         d->id == lfs_tag_id(attrs[i].tag)) { | ||||
| @@ -1825,9 +1857,8 @@ compact: | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     lfs_block_t pair[2] = {dir->pair[0], dir->pair[1]}; | ||||
|     for (struct lfs_mlist *d = lfs->mlist; d; d = d->next) { | ||||
|         if (lfs_pair_cmp(d->m.pair, pair) == 0) { | ||||
|         if (lfs_pair_cmp(d->m.pair, olddir.pair) == 0) { | ||||
|             while (d->id >= d->m.count && d->m.split) { | ||||
|                 // we split and id is on tail now | ||||
|                 d->id -= d->m.count; | ||||
| @@ -2204,16 +2235,16 @@ static int lfs_ctz_extend(lfs_t *lfs, | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             size -= 1; | ||||
|             lfs_off_t index = lfs_ctz_index(lfs, &size); | ||||
|             size += 1; | ||||
|             lfs_size_t noff = size - 1; | ||||
|             lfs_off_t index = lfs_ctz_index(lfs, &noff); | ||||
|             noff = noff + 1; | ||||
|  | ||||
|             // just copy out the last block if it is incomplete | ||||
|             if (size != lfs->cfg->block_size) { | ||||
|                 for (lfs_off_t i = 0; i < size; i++) { | ||||
|             if (noff != lfs->cfg->block_size) { | ||||
|                 for (lfs_off_t i = 0; i < noff; i++) { | ||||
|                     uint8_t data; | ||||
|                     err = lfs_bd_read(lfs, | ||||
|                             NULL, rcache, size-i, | ||||
|                             NULL, rcache, noff-i, | ||||
|                             head, i, &data, 1); | ||||
|                     if (err) { | ||||
|                         return err; | ||||
| @@ -2231,19 +2262,19 @@ static int lfs_ctz_extend(lfs_t *lfs, | ||||
|                 } | ||||
|  | ||||
|                 *block = nblock; | ||||
|                 *off = size; | ||||
|                 *off = noff; | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // append block | ||||
|             index += 1; | ||||
|             lfs_size_t skips = lfs_ctz(index) + 1; | ||||
|  | ||||
|             lfs_block_t nhead = head; | ||||
|             for (lfs_off_t i = 0; i < skips; i++) { | ||||
|                 head = lfs_tole32(head); | ||||
|                 nhead = lfs_tole32(nhead); | ||||
|                 err = lfs_bd_prog(lfs, pcache, rcache, true, | ||||
|                         nblock, 4*i, &head, 4); | ||||
|                 head = lfs_fromle32(head); | ||||
|                         nblock, 4*i, &nhead, 4); | ||||
|                 nhead = lfs_fromle32(nhead); | ||||
|                 if (err) { | ||||
|                     if (err == LFS_ERR_CORRUPT) { | ||||
|                         goto relocate; | ||||
| @@ -2253,15 +2284,15 @@ static int lfs_ctz_extend(lfs_t *lfs, | ||||
|  | ||||
|                 if (i != skips-1) { | ||||
|                     err = lfs_bd_read(lfs, | ||||
|                             NULL, rcache, sizeof(head), | ||||
|                             head, 4*i, &head, sizeof(head)); | ||||
|                     head = lfs_fromle32(head); | ||||
|                             NULL, rcache, sizeof(nhead), | ||||
|                             nhead, 4*i, &nhead, sizeof(nhead)); | ||||
|                     nhead = lfs_fromle32(nhead); | ||||
|                     if (err) { | ||||
|                         return err; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); | ||||
|                 LFS_ASSERT(nhead >= 2 && nhead <= lfs->cfg->block_count); | ||||
|             } | ||||
|  | ||||
|             *block = nblock; | ||||
| @@ -2677,66 +2708,52 @@ int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) { | ||||
|     LFS_TRACE("lfs_file_sync(%p, %p)", (void*)lfs, (void*)file); | ||||
|     LFS_ASSERT(file->flags & LFS_F_OPENED); | ||||
|  | ||||
|     while (true) { | ||||
|         int err = lfs_file_flush(lfs, file); | ||||
|         if (err) { | ||||
|             file->flags |= LFS_F_ERRED; | ||||
|             LFS_TRACE("lfs_file_sync -> %d", err); | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         if ((file->flags & LFS_F_DIRTY) && | ||||
|                 !(file->flags & LFS_F_ERRED) && | ||||
|                 !lfs_pair_isnull(file->m.pair)) { | ||||
|             // update dir entry | ||||
|             uint16_t type; | ||||
|             const void *buffer; | ||||
|             lfs_size_t size; | ||||
|             struct lfs_ctz ctz; | ||||
|             if (file->flags & LFS_F_INLINE) { | ||||
|                 // inline the whole file | ||||
|                 type = LFS_TYPE_INLINESTRUCT; | ||||
|                 buffer = file->cache.buffer; | ||||
|                 size = file->ctz.size; | ||||
|             } else { | ||||
|                 // update the ctz reference | ||||
|                 type = LFS_TYPE_CTZSTRUCT; | ||||
|                 // copy ctz so alloc will work during a relocate | ||||
|                 ctz = file->ctz; | ||||
|                 lfs_ctz_tole32(&ctz); | ||||
|                 buffer = &ctz; | ||||
|                 size = sizeof(ctz); | ||||
|             } | ||||
|  | ||||
|             // commit file data and attributes | ||||
|             err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( | ||||
|                     {LFS_MKTAG(type, file->id, size), buffer}, | ||||
|                     {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, | ||||
|                         file->cfg->attr_count), file->cfg->attrs})); | ||||
|             if (err) { | ||||
|                 if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) { | ||||
|                     goto relocate; | ||||
|                 } | ||||
|                 file->flags |= LFS_F_ERRED; | ||||
|                 LFS_TRACE("lfs_file_sync -> %d", err); | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             file->flags &= ~LFS_F_DIRTY; | ||||
|         } | ||||
|  | ||||
|         LFS_TRACE("lfs_file_sync -> %d", 0); | ||||
|         return 0; | ||||
|  | ||||
| relocate: | ||||
|         // inline file doesn't fit anymore | ||||
|         err = lfs_file_outline(lfs, file); | ||||
|         if (err) { | ||||
|             file->flags |= LFS_F_ERRED; | ||||
|             LFS_TRACE("lfs_file_sync -> %d", err); | ||||
|             return err; | ||||
|         } | ||||
|     int err = lfs_file_flush(lfs, file); | ||||
|     if (err) { | ||||
|         file->flags |= LFS_F_ERRED; | ||||
|         LFS_TRACE("lfs_file_sync -> %d", err); | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     if ((file->flags & LFS_F_DIRTY) && | ||||
|             !(file->flags & LFS_F_ERRED) && | ||||
|             !lfs_pair_isnull(file->m.pair)) { | ||||
|         // update dir entry | ||||
|         uint16_t type; | ||||
|         const void *buffer; | ||||
|         lfs_size_t size; | ||||
|         struct lfs_ctz ctz; | ||||
|         if (file->flags & LFS_F_INLINE) { | ||||
|             // inline the whole file | ||||
|             type = LFS_TYPE_INLINESTRUCT; | ||||
|             buffer = file->cache.buffer; | ||||
|             size = file->ctz.size; | ||||
|         } else { | ||||
|             // update the ctz reference | ||||
|             type = LFS_TYPE_CTZSTRUCT; | ||||
|             // copy ctz so alloc will work during a relocate | ||||
|             ctz = file->ctz; | ||||
|             lfs_ctz_tole32(&ctz); | ||||
|             buffer = &ctz; | ||||
|             size = sizeof(ctz); | ||||
|         } | ||||
|  | ||||
|         // commit file data and attributes | ||||
|         err = lfs_dir_commit(lfs, &file->m, LFS_MKATTRS( | ||||
|                 {LFS_MKTAG(type, file->id, size), buffer}, | ||||
|                 {LFS_MKTAG(LFS_FROM_USERATTRS, file->id, | ||||
|                     file->cfg->attr_count), file->cfg->attrs})); | ||||
|         if (err) { | ||||
|             file->flags |= LFS_F_ERRED; | ||||
|             LFS_TRACE("lfs_file_sync -> %d", err); | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         file->flags &= ~LFS_F_DIRTY; | ||||
|     } | ||||
|  | ||||
|     LFS_TRACE("lfs_file_sync -> %d", 0); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file, | ||||
| @@ -3764,10 +3781,9 @@ int lfs_unmount(lfs_t *lfs) { | ||||
|  | ||||
|  | ||||
| /// Filesystem filesystem operations /// | ||||
| int lfs_fs_traverse(lfs_t *lfs, | ||||
|         int (*cb)(void *data, lfs_block_t block), void *data) { | ||||
|     LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", | ||||
|             (void*)lfs, (void*)(uintptr_t)cb, data); | ||||
| int lfs_fs_traverseraw(lfs_t *lfs, | ||||
|         int (*cb)(void *data, lfs_block_t block), void *data, | ||||
|         bool includeorphans) { | ||||
|     // iterate over metadata pairs | ||||
|     lfs_mdir_t dir = {.tail = {0, 1}}; | ||||
|  | ||||
| @@ -3776,7 +3792,6 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|     if (lfs->lfs1) { | ||||
|         int err = lfs1_traverse(lfs, cb, data); | ||||
|         if (err) { | ||||
|             LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
| @@ -3789,7 +3804,6 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|         for (int i = 0; i < 2; i++) { | ||||
|             int err = cb(data, dir.tail[i]); | ||||
|             if (err) { | ||||
|                 LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
| @@ -3797,7 +3811,6 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|         // iterate through ids in directory | ||||
|         int err = lfs_dir_fetch(lfs, &dir, dir.tail); | ||||
|         if (err) { | ||||
|             LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
| @@ -3809,7 +3822,6 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|                 if (tag == LFS_ERR_NOENT) { | ||||
|                     continue; | ||||
|                 } | ||||
|                 LFS_TRACE("lfs_fs_traverse -> %"PRId32, tag); | ||||
|                 return tag; | ||||
|             } | ||||
|             lfs_ctz_fromle32(&ctz); | ||||
| @@ -3818,9 +3830,16 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|                 err = lfs_ctz_traverse(lfs, NULL, &lfs->rcache, | ||||
|                         ctz.head, ctz.size, cb, data); | ||||
|                 if (err) { | ||||
|                     LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|                     return err; | ||||
|                 } | ||||
|             } else if (includeorphans &&  | ||||
|                     lfs_tag_type3(tag) == LFS_TYPE_DIRSTRUCT) { | ||||
|                 for (int i = 0; i < 2; i++) { | ||||
|                     err = cb(data, (&ctz.head)[i]); | ||||
|                     if (err) { | ||||
|                         return err; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -3835,7 +3854,6 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|             int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, | ||||
|                     f->ctz.head, f->ctz.size, cb, data); | ||||
|             if (err) { | ||||
|                 LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
| @@ -3844,16 +3862,23 @@ int lfs_fs_traverse(lfs_t *lfs, | ||||
|             int err = lfs_ctz_traverse(lfs, &f->cache, &lfs->rcache, | ||||
|                     f->block, f->pos, cb, data); | ||||
|             if (err) { | ||||
|                 LFS_TRACE("lfs_fs_traverse -> %d", err); | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     LFS_TRACE("lfs_fs_traverse -> %d", 0); | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| int lfs_fs_traverse(lfs_t *lfs, | ||||
|         int (*cb)(void *data, lfs_block_t block), void *data) { | ||||
|     LFS_TRACE("lfs_fs_traverse(%p, %p, %p)", | ||||
|             (void*)lfs, (void*)(uintptr_t)cb, data); | ||||
|     int err = lfs_fs_traverseraw(lfs, cb, data, true); | ||||
|     LFS_TRACE("lfs_fs_traverse -> %d", 0); | ||||
|     return err; | ||||
| } | ||||
|  | ||||
| static int lfs_fs_pred(lfs_t *lfs, | ||||
|         const lfs_block_t pair[2], lfs_mdir_t *pdir) { | ||||
|     // iterate over all directory directory entries | ||||
| @@ -3902,7 +3927,9 @@ static lfs_stag_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2], | ||||
|     // use fetchmatch with callback to find pairs | ||||
|     parent->tail[0] = 0; | ||||
|     parent->tail[1] = 1; | ||||
|     int i = 0; | ||||
|     while (!lfs_pair_isnull(parent->tail)) { | ||||
|         i += 1; | ||||
|         lfs_stag_t tag = lfs_dir_fetchmatch(lfs, parent, parent->tail, | ||||
|                 LFS_MKTAG(0x7ff, 0, 0x3ff), | ||||
|                 LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, 8), | ||||
| @@ -4157,7 +4184,7 @@ static int lfs_fs_size_count(void *p, lfs_block_t block) { | ||||
| lfs_ssize_t lfs_fs_size(lfs_t *lfs) { | ||||
|     LFS_TRACE("lfs_fs_size(%p)", (void*)lfs); | ||||
|     lfs_size_t size = 0; | ||||
|     int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size); | ||||
|     int err = lfs_fs_traverseraw(lfs, lfs_fs_size_count, &size, false); | ||||
|     if (err) { | ||||
|         LFS_TRACE("lfs_fs_size -> %d", err); | ||||
|         return err; | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| import struct | ||||
| import binascii | ||||
| import sys | ||||
| import itertools as it | ||||
|  | ||||
| TAG_TYPES = { | ||||
| @@ -271,37 +272,39 @@ class MetadataPair: | ||||
|  | ||||
|         raise KeyError(gmask, gtag) | ||||
|  | ||||
|     def _dump_tags(self, tags, truncate=True): | ||||
|         sys.stdout.write("%-8s  %-8s  %-13s %4s %4s  %s\n" % ( | ||||
|             'off', 'tag', 'type', 'id', 'len', | ||||
|             'data (truncated)' if truncate else 12*' '+'data')) | ||||
|     def _dump_tags(self, tags, f=sys.stdout, truncate=True): | ||||
|         f.write("%-8s  %-8s  %-13s %4s %4s" % ( | ||||
|             'off', 'tag', 'type', 'id', 'len')) | ||||
|         if truncate: | ||||
|             f.write('  data (truncated)') | ||||
|         f.write('\n') | ||||
|  | ||||
|         for tag in tags: | ||||
|             sys.stdout.write("%08x: %08x  %-13s %4s %4s" % ( | ||||
|             f.write("%08x: %08x  %-13s %4s %4s" % ( | ||||
|                 tag.off, tag, | ||||
|                 tag.typerepr(), tag.idrepr(), tag.sizerepr())) | ||||
|             if truncate: | ||||
|                 sys.stdout.write("  %-23s  %-8s\n" % ( | ||||
|                 f.write("  %-23s  %-8s\n" % ( | ||||
|                     ' '.join('%02x' % c for c in tag.data[:8]), | ||||
|                     ''.join(c if c >= ' ' and c <= '~' else '.' | ||||
|                         for c in map(chr, tag.data[:8])))) | ||||
|             else: | ||||
|                 sys.stdout.write("\n") | ||||
|                 f.write("\n") | ||||
|                 for i in range(0, len(tag.data), 16): | ||||
|                     sys.stdout.write("%08x: %-47s  %-16s\n" % ( | ||||
|                     f.write("  %08x: %-47s  %-16s\n" % ( | ||||
|                         tag.off+i, | ||||
|                         ' '.join('%02x' % c for c in tag.data[i:i+16]), | ||||
|                         ''.join(c if c >= ' ' and c <= '~' else '.' | ||||
|                             for c in map(chr, tag.data[i:i+16])))) | ||||
|  | ||||
|     def dump_tags(self, truncate=True): | ||||
|         self._dump_tags(self.tags, truncate=truncate) | ||||
|     def dump_tags(self, f=sys.stdout, truncate=True): | ||||
|         self._dump_tags(self.tags, f=f, truncate=truncate) | ||||
|  | ||||
|     def dump_log(self, truncate=True): | ||||
|         self._dump_tags(self.log, truncate=truncate) | ||||
|     def dump_log(self, f=sys.stdout, truncate=True): | ||||
|         self._dump_tags(self.log, f=f, truncate=truncate) | ||||
|  | ||||
|     def dump_all(self, truncate=True): | ||||
|         self._dump_tags(self.all_, truncate=truncate) | ||||
|     def dump_all(self, f=sys.stdout, truncate=True): | ||||
|         self._dump_tags(self.all_, f=f, truncate=truncate) | ||||
|  | ||||
| def main(args): | ||||
|     blocks = [] | ||||
| @@ -337,10 +340,10 @@ if __name__ == "__main__": | ||||
|         help="First block address for finding the metadata pair.") | ||||
|     parser.add_argument('block2', nargs='?', type=lambda x: int(x, 0), | ||||
|         help="Second block address for finding the metadata pair.") | ||||
|     parser.add_argument('-a', '--all', action='store_true', | ||||
|         help="Show all tags in log, included tags in corrupted commits.") | ||||
|     parser.add_argument('-l', '--log', action='store_true', | ||||
|         help="Show tags in log.") | ||||
|     parser.add_argument('-a', '--all', action='store_true', | ||||
|         help="Show all tags in log, included tags in corrupted commits.") | ||||
|     parser.add_argument('-T', '--no-truncate', action='store_true', | ||||
|         help="Don't truncate large amounts of data in tags.") | ||||
|         help="Don't truncate large amounts of data.") | ||||
|     sys.exit(main(parser.parse_args())) | ||||
|   | ||||
| @@ -13,45 +13,6 @@ def popc(x): | ||||
| def ctz(x): | ||||
|     return len(bin(x)) - len(bin(x).rstrip('0')) | ||||
|  | ||||
| def dumptags(args, mdir, f): | ||||
|     if args.all: | ||||
|         tags = mdir.all_ | ||||
|     elif args.log: | ||||
|         tags = mdir.log | ||||
|     else: | ||||
|         tags = mdir.tags | ||||
|  | ||||
|     for k, tag in enumerate(tags): | ||||
|         f.write("tag %08x %s" % (tag, tag.typerepr())) | ||||
|         if tag.id != 0x3ff: | ||||
|             f.write(" id %d" % tag.id) | ||||
|         if tag.size != 0x3ff: | ||||
|             f.write(" size %d" % tag.size) | ||||
|         if tag.is_('name'): | ||||
|             f.write(" name %s" % | ||||
|                 json.dumps(tag.data.decode('utf8'))) | ||||
|         if tag.is_('dirstruct'): | ||||
|             f.write(" dir {%#x, %#x}" % struct.unpack( | ||||
|                 '<II', tag.data[:8].ljust(8, b'\xff'))) | ||||
|         if tag.is_('ctzstruct'): | ||||
|             f.write(" ctz {%#x} size %d" % struct.unpack( | ||||
|                 '<II', tag.data[:8].ljust(8, b'\xff'))) | ||||
|         if tag.is_('inlinestruct'): | ||||
|             f.write(" inline size %d" % tag.size) | ||||
|         if tag.is_('gstate'): | ||||
|             f.write(" 0x%s" % ''.join('%02x' % c for c in tag.data)) | ||||
|         if tag.is_('tail'): | ||||
|             f.write(" tail {%#x, %#x}" % struct.unpack( | ||||
|                 '<II', tag.data[:8].ljust(8, b'\xff'))) | ||||
|         f.write("\n") | ||||
|  | ||||
|         if args.data: | ||||
|             for i in range(0, len(tag.data), 16): | ||||
|                 f.write("  %-47s  %-16s\n" % ( | ||||
|                     ' '.join('%02x' % c for c in tag.data[i:i+16]), | ||||
|                     ''.join(c if c >= ' ' and c <= '~' else '.' | ||||
|                         for c in map(chr, tag.data[i:i+16])))) | ||||
|  | ||||
| def dumpentries(args, mdir, f): | ||||
|     for k, id_ in enumerate(mdir.ids): | ||||
|         name = mdir[Tag('name', id_, 0)] | ||||
| @@ -72,8 +33,8 @@ def dumpentries(args, mdir, f): | ||||
|  | ||||
|         if args.data and struct_.is_('inlinestruct'): | ||||
|             for i in range(0, len(struct_.data), 16): | ||||
|                 f.write("  %-47s  %-16s\n" % ( | ||||
|                     ' '.join('%02x' % c for c in struct_.data[i:i+16]), | ||||
|                 f.write("  %08x: %-47s  %-16s\n" % ( | ||||
|                     i, ' '.join('%02x' % c for c in struct_.data[i:i+16]), | ||||
|                     ''.join(c if c >= ' ' and c <= '~' else '.' | ||||
|                         for c in map(chr, struct_.data[i:i+16])))) | ||||
|         elif args.data and struct_.is_('ctzstruct'): | ||||
| @@ -95,8 +56,8 @@ def dumpentries(args, mdir, f): | ||||
|                 it.chain.from_iterable(reversed(data)), size)) | ||||
|             for i in range(0, min(len(data), 256) | ||||
|                     if not args.no_truncate else len(data), 16): | ||||
|                 f.write("  %-47s  %-16s\n" % ( | ||||
|                     ' '.join('%02x' % c for c in data[i:i+16]), | ||||
|                 f.write("  %08x: %-47s  %-16s\n" % ( | ||||
|                     i, ' '.join('%02x' % c for c in data[i:i+16]), | ||||
|                     ''.join(c if c >= ' ' and c <= '~' else '.' | ||||
|                         for c in map(chr, data[i:i+16])))) | ||||
|  | ||||
| @@ -118,9 +79,17 @@ def main(args): | ||||
|         superblock = None | ||||
|         gstate = b'' | ||||
|         mdirs = [] | ||||
|         cycle = False | ||||
|         tail = (args.block1, args.block2) | ||||
|         hard = False | ||||
|         while True: | ||||
|             for m in it.chain((m for d in dirs for m in d), mdirs): | ||||
|                 if set(m.blocks) == set(tail): | ||||
|                     # cycle detected | ||||
|                     cycle = m.blocks | ||||
|             if cycle: | ||||
|                 break | ||||
|  | ||||
|             # load mdir | ||||
|             data = [] | ||||
|             blocks = {} | ||||
| @@ -129,6 +98,7 @@ def main(args): | ||||
|                 data.append(f.read(args.block_size) | ||||
|                     .ljust(args.block_size, b'\xff')) | ||||
|                 blocks[id(data[-1])] = block | ||||
|  | ||||
|             mdir = MetadataPair(data) | ||||
|             mdir.blocks = tuple(blocks[id(p.data)] for p in mdir.pair) | ||||
|  | ||||
| @@ -171,7 +141,7 @@ def main(args): | ||||
|     # find paths | ||||
|     dirtable = {} | ||||
|     for dir in dirs: | ||||
|         dirtable[tuple(sorted(dir[0].blocks))] = dir | ||||
|         dirtable[frozenset(dir[0].blocks)] = dir | ||||
|  | ||||
|     pending = [("/", dirs[0])] | ||||
|     while pending: | ||||
| @@ -183,7 +153,7 @@ def main(args): | ||||
|                         npath = tag.data.decode('utf8') | ||||
|                         dirstruct = mdir[Tag('dirstruct', tag.id, 0)] | ||||
|                         nblocks = struct.unpack('<II', dirstruct.data) | ||||
|                         nmdir = dirtable[tuple(sorted(nblocks))] | ||||
|                         nmdir = dirtable[frozenset(nblocks)] | ||||
|                         pending.append(((path + '/' + npath), nmdir)) | ||||
|                     except KeyError: | ||||
|                         pass | ||||
| @@ -230,8 +200,12 @@ def main(args): | ||||
|                     ' (corrupted)' if not mdir else '')) | ||||
|  | ||||
|                 f = io.StringIO() | ||||
|                 if args.tags or args.all or args.log: | ||||
|                     dumptags(args, mdir, f) | ||||
|                 if args.tags: | ||||
|                     mdir.dump_tags(f, truncate=not args.no_truncate) | ||||
|                 elif args.log: | ||||
|                     mdir.dump_log(f, truncate=not args.no_truncate) | ||||
|                 elif args.all: | ||||
|                     mdir.dump_all(f, truncate=not args.no_truncate) | ||||
|                 else: | ||||
|                     dumpentries(args, mdir, f) | ||||
|  | ||||
| @@ -243,7 +217,15 @@ def main(args): | ||||
|                         '|', | ||||
|                         line)) | ||||
|  | ||||
|     return 0 if all(mdir for dir in dirs for mdir in dir) else 1 | ||||
|     if cycle: | ||||
|         print("*** cycle detected! -> {%#x, %#x} ***" % (cycle[0], cycle[1])) | ||||
|  | ||||
|     if cycle: | ||||
|         return 2 | ||||
|     elif not all(mdir for dir in dirs for mdir in dir): | ||||
|         return 1 | ||||
|     else: | ||||
|         return 0; | ||||
|  | ||||
| if __name__ == "__main__": | ||||
|     import argparse | ||||
| @@ -268,12 +250,12 @@ if __name__ == "__main__": | ||||
|         help="Show contents of metadata-pairs/directories.") | ||||
|     parser.add_argument('-t', '--tags', action='store_true', | ||||
|         help="Show metadata tags instead of reconstructing entries.") | ||||
|     parser.add_argument('-a', '--all', action='store_true', | ||||
|         help="Show all tags in log, included tags in corrupted commits.") | ||||
|     parser.add_argument('-l', '--log', action='store_true', | ||||
|         help="Show tags in log.") | ||||
|     parser.add_argument('-a', '--all', action='store_true', | ||||
|         help="Show all tags in log, included tags in corrupted commits.") | ||||
|     parser.add_argument('-d', '--data', action='store_true', | ||||
|         help="Also show the raw contents of files/attrs/tags.") | ||||
|     parser.add_argument('-T', '--no-truncate', action='store_true', | ||||
|         help="Don't truncate large amounts of data in files.") | ||||
|         help="Don't truncate large amounts of data.") | ||||
|     sys.exit(main(parser.parse_args())) | ||||
|   | ||||
| @@ -52,7 +52,7 @@ DEFINES = { | ||||
|     'LFS_LOOKAHEAD_SIZE': 16, | ||||
|     'LFS_ERASE_VALUE': 0xff, | ||||
|     'LFS_ERASE_CYCLES': 0, | ||||
|     'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_BADBLOCK_BEHAVIOR': 'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
| } | ||||
| PROLOGUE = """ | ||||
|     // prologue | ||||
| @@ -182,7 +182,19 @@ class TestCase: | ||||
|         elif args.get('no_internal', False) and self.in_ is not None: | ||||
|             return False | ||||
|         elif self.if_ is not None: | ||||
|             return eval(self.if_, None, self.defines.copy()) | ||||
|             if_ = self.if_ | ||||
|             while True: | ||||
|                 for k, v in self.defines.items(): | ||||
|                     if k in if_: | ||||
|                         if_ = if_.replace(k, '(%s)' % v) | ||||
|                         break | ||||
|                 else: | ||||
|                     break | ||||
|             if_ = ( | ||||
|                 re.sub('(\&\&|\?)', ' and ', | ||||
|                 re.sub('(\|\||:)', ' or ', | ||||
|                 re.sub('!(?!=)', ' not ', if_)))) | ||||
|             return eval(if_) | ||||
|         else: | ||||
|             return True | ||||
|  | ||||
| @@ -235,33 +247,37 @@ class TestCase: | ||||
|         mpty = os.fdopen(mpty, 'r', 1) | ||||
|         stdout = [] | ||||
|         assert_ = None | ||||
|         while True: | ||||
|             try: | ||||
|                 line = mpty.readline() | ||||
|             except OSError as e: | ||||
|                 if e.errno == errno.EIO: | ||||
|                     break | ||||
|                 raise | ||||
|             stdout.append(line) | ||||
|             if args.get('verbose', False): | ||||
|                 sys.stdout.write(line) | ||||
|             # intercept asserts | ||||
|             m = re.match( | ||||
|                 '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$' | ||||
|                 .format('(?:\033\[[\d;]*.| )*', 'assert'), | ||||
|                 line) | ||||
|             if m and assert_ is None: | ||||
|         try: | ||||
|             while True: | ||||
|                 try: | ||||
|                     with open(m.group(1)) as f: | ||||
|                         lineno = int(m.group(2)) | ||||
|                         line = next(it.islice(f, lineno-1, None)).strip('\n') | ||||
|                     assert_ = { | ||||
|                         'path': m.group(1), | ||||
|                         'line': line, | ||||
|                         'lineno': lineno, | ||||
|                         'message': m.group(3)} | ||||
|                 except: | ||||
|                     pass | ||||
|                     line = mpty.readline() | ||||
|                 except OSError as e: | ||||
|                     if e.errno == errno.EIO: | ||||
|                         break | ||||
|                     raise | ||||
|                 stdout.append(line) | ||||
|                 if args.get('verbose', False): | ||||
|                     sys.stdout.write(line) | ||||
|                 # intercept asserts | ||||
|                 m = re.match( | ||||
|                     '^{0}([^:]+):(\d+):(?:\d+:)?{0}{1}:{0}(.*)$' | ||||
|                     .format('(?:\033\[[\d;]*.| )*', 'assert'), | ||||
|                     line) | ||||
|                 if m and assert_ is None: | ||||
|                     try: | ||||
|                         with open(m.group(1)) as f: | ||||
|                             lineno = int(m.group(2)) | ||||
|                             line = (next(it.islice(f, lineno-1, None)) | ||||
|                                 .strip('\n')) | ||||
|                         assert_ = { | ||||
|                             'path': m.group(1), | ||||
|                             'line': line, | ||||
|                             'lineno': lineno, | ||||
|                             'message': m.group(3)} | ||||
|                     except: | ||||
|                         pass | ||||
|         except KeyboardInterrupt: | ||||
|             raise TestFailure(self, 1, stdout, None) | ||||
|         proc.wait() | ||||
|  | ||||
|         # did we pass? | ||||
| @@ -654,6 +670,10 @@ def main(**args): | ||||
|     if filtered != sum(len(suite.perms) for suite in suites): | ||||
|         print('filtered down to %d permutations' % filtered) | ||||
|  | ||||
|     # only requested to build? | ||||
|     if args.get('build', False): | ||||
|         return 0 | ||||
|  | ||||
|     print('====== testing ======') | ||||
|     try: | ||||
|         for suite in suites: | ||||
| @@ -678,18 +698,19 @@ def main(**args): | ||||
|                         perm=perm, path=perm.suite.path, lineno=perm.lineno, | ||||
|                         returncode=perm.result.returncode or 0)) | ||||
|                 if perm.result.stdout: | ||||
|                     for line in (perm.result.stdout | ||||
|                             if not perm.result.assert_ | ||||
|                             else perm.result.stdout[:-1]): | ||||
|                     if perm.result.assert_: | ||||
|                         stdout = perm.result.stdout[:-1] | ||||
|                     else: | ||||
|                         stdout = perm.result.stdout | ||||
|                     if (not args.get('verbose', False) and len(stdout) > 5): | ||||
|                         sys.stdout.write('...\n') | ||||
|                     for line in stdout[-5:]: | ||||
|                         sys.stdout.write(line) | ||||
|                 if perm.result.assert_: | ||||
|                     sys.stdout.write( | ||||
|                         "\033[01m{path}:{lineno}:\033[01;31massert:\033[m " | ||||
|                         "{message}\n{line}\n".format( | ||||
|                             **perm.result.assert_)) | ||||
|                 else: | ||||
|                     for line in perm.result.stdout: | ||||
|                         sys.stdout.write(line) | ||||
|                 sys.stdout.write('\n') | ||||
|                 failed += 1 | ||||
|  | ||||
| @@ -728,6 +749,8 @@ if __name__ == "__main__": | ||||
|     parser.add_argument('-p', '--persist', choices=['erase', 'noerase'], | ||||
|         nargs='?', const='erase', | ||||
|         help="Store disk image in a file.") | ||||
|     parser.add_argument('-b', '--build', action='store_true', | ||||
|         help="Only build the tests, do not execute.") | ||||
|     parser.add_argument('-g', '--gdb', choices=['init', 'start', 'assert'], | ||||
|         nargs='?', const='assert', | ||||
|         help="Drop into gdb on test failure.") | ||||
|   | ||||
| @@ -329,7 +329,7 @@ code = ''' | ||||
| [[case]] # chained dir exhaustion test | ||||
| define.LFS_BLOCK_SIZE = 512 | ||||
| define.LFS_BLOCK_COUNT = 1024 | ||||
| if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024' | ||||
| if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
| @@ -400,7 +400,7 @@ code = ''' | ||||
| [[case]] # split dir test | ||||
| define.LFS_BLOCK_SIZE = 512 | ||||
| define.LFS_BLOCK_COUNT = 1024 | ||||
| if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024' | ||||
| if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
| @@ -445,7 +445,7 @@ code = ''' | ||||
| [[case]] # outdated lookahead test | ||||
| define.LFS_BLOCK_SIZE = 512 | ||||
| define.LFS_BLOCK_COUNT = 1024 | ||||
| if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024' | ||||
| if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
| @@ -510,7 +510,7 @@ code = ''' | ||||
| [[case]] # outdated lookahead and split dir test | ||||
| define.LFS_BLOCK_SIZE = 512 | ||||
| define.LFS_BLOCK_COUNT = 1024 | ||||
| if = 'LFS_BLOCK_SIZE == 512 and LFS_BLOCK_COUNT == 1024' | ||||
| if = 'LFS_BLOCK_SIZE == 512 && LFS_BLOCK_COUNT == 1024' | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
|   | ||||
| @@ -1,6 +1,14 @@ | ||||
| [[case]] # single bad blocks | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOPROG' | ||||
| define.LFS_ERASE_VALUE = [0x00, 0xff, -1] | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| define.NAMEMULT = 64 | ||||
| define.FILEMULT = 1 | ||||
| code = ''' | ||||
| @@ -64,144 +72,16 @@ code = ''' | ||||
|     } | ||||
| ''' | ||||
|  | ||||
| [[case]] # single persistent blocks (can't erase) | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOERASE' | ||||
| define.NAMEMULT = 64 | ||||
| define.FILEMULT = 1 | ||||
| code = ''' | ||||
|     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT; badblock++) { | ||||
|         lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; | ||||
|         lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; | ||||
|          | ||||
|         lfs_format(&lfs, &cfg) => 0; | ||||
|  | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         for (int i = 1; i < 10; i++) { | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j] = '0'+i; | ||||
|             } | ||||
|             buffer[NAMEMULT] = '\0'; | ||||
|             lfs_mkdir(&lfs, (char*)buffer) => 0; | ||||
|  | ||||
|             buffer[NAMEMULT] = '/'; | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j+NAMEMULT+1] = '0'+i; | ||||
|             } | ||||
|             buffer[2*NAMEMULT+1] = '\0'; | ||||
|             lfs_file_open(&lfs, &file, (char*)buffer, | ||||
|                     LFS_O_WRONLY | LFS_O_CREAT) => 0; | ||||
|              | ||||
|             size = NAMEMULT; | ||||
|             for (int j = 0; j < i*FILEMULT; j++) { | ||||
|                 lfs_file_write(&lfs, &file, buffer, size) => size; | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         for (int i = 1; i < 10; i++) { | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j] = '0'+i; | ||||
|             } | ||||
|             buffer[NAMEMULT] = '\0'; | ||||
|             lfs_stat(&lfs, (char*)buffer, &info) => 0; | ||||
|             info.type => LFS_TYPE_DIR; | ||||
|  | ||||
|             buffer[NAMEMULT] = '/'; | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j+NAMEMULT+1] = '0'+i; | ||||
|             } | ||||
|             buffer[2*NAMEMULT+1] = '\0'; | ||||
|             lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; | ||||
|              | ||||
|             size = NAMEMULT; | ||||
|             for (int j = 0; j < i*FILEMULT; j++) { | ||||
|                 uint8_t rbuffer[1024]; | ||||
|                 lfs_file_read(&lfs, &file, rbuffer, size) => size; | ||||
|                 memcmp(buffer, rbuffer, size) => 0; | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|     } | ||||
| ''' | ||||
|  | ||||
| [[case]] # single unreadable blocks (can't read) | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_BADBLOCK_BEHAVIOR = 'LFS_TESTBD_BADBLOCK_NOREAD' | ||||
| define.NAMEMULT = 64 | ||||
| define.FILEMULT = 1 | ||||
| code = ''' | ||||
|     for (lfs_block_t badblock = 2; badblock < LFS_BLOCK_COUNT/2; badblock++) { | ||||
|         lfs_testbd_setwear(&cfg, badblock-1, 0) => 0; | ||||
|         lfs_testbd_setwear(&cfg, badblock, 0xffffffff) => 0; | ||||
|          | ||||
|         lfs_format(&lfs, &cfg) => 0; | ||||
|  | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         for (int i = 1; i < 10; i++) { | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j] = '0'+i; | ||||
|             } | ||||
|             buffer[NAMEMULT] = '\0'; | ||||
|             lfs_mkdir(&lfs, (char*)buffer) => 0; | ||||
|  | ||||
|             buffer[NAMEMULT] = '/'; | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j+NAMEMULT+1] = '0'+i; | ||||
|             } | ||||
|             buffer[2*NAMEMULT+1] = '\0'; | ||||
|             lfs_file_open(&lfs, &file, (char*)buffer, | ||||
|                     LFS_O_WRONLY | LFS_O_CREAT) => 0; | ||||
|              | ||||
|             size = NAMEMULT; | ||||
|             for (int j = 0; j < i*FILEMULT; j++) { | ||||
|                 lfs_file_write(&lfs, &file, buffer, size) => size; | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         for (int i = 1; i < 10; i++) { | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j] = '0'+i; | ||||
|             } | ||||
|             buffer[NAMEMULT] = '\0'; | ||||
|             lfs_stat(&lfs, (char*)buffer, &info) => 0; | ||||
|             info.type => LFS_TYPE_DIR; | ||||
|  | ||||
|             buffer[NAMEMULT] = '/'; | ||||
|             for (int j = 0; j < NAMEMULT; j++) { | ||||
|                 buffer[j+NAMEMULT+1] = '0'+i; | ||||
|             } | ||||
|             buffer[2*NAMEMULT+1] = '\0'; | ||||
|             lfs_file_open(&lfs, &file, (char*)buffer, LFS_O_RDONLY) => 0; | ||||
|              | ||||
|             size = NAMEMULT; | ||||
|             for (int j = 0; j < i*FILEMULT; j++) { | ||||
|                 uint8_t rbuffer[1024]; | ||||
|                 lfs_file_read(&lfs, &file, rbuffer, size) => size; | ||||
|                 memcmp(buffer, rbuffer, size) => 0; | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|     } | ||||
| ''' | ||||
|  | ||||
| [[case]] # region corruption (causes cascading failures) | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_ERASE_VALUE = [0x00, 0xff, -1] | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| define.NAMEMULT = 64 | ||||
| define.FILEMULT = 1 | ||||
| @@ -266,11 +146,15 @@ code = ''' | ||||
| ''' | ||||
|  | ||||
| [[case]] # alternating corruption (causes cascading failures) | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_ERASE_VALUE = [0x00, 0xff, -1] | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| define.NAMEMULT = 64 | ||||
| define.FILEMULT = 1 | ||||
| @@ -337,10 +221,13 @@ code = ''' | ||||
| # other corner cases | ||||
| [[case]] # bad superblocks (corrupt 1 or 0) | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_ERASE_VALUE = [0x00, 0xff, -1] | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| code = ''' | ||||
|     lfs_testbd_setwear(&cfg, 0, 0xffffffff) => 0; | ||||
|   | ||||
| @@ -155,7 +155,7 @@ code = ''' | ||||
| ''' | ||||
|  | ||||
| [[case]] # reentrant many directory creation/rename/removal | ||||
| define.N = [5, 25] | ||||
| define.N = [5, 10] # TODO changed from 20, should we be able to do more? | ||||
| reentrant = true | ||||
| code = ''' | ||||
|     err = lfs_mount(&lfs, &cfg); | ||||
|   | ||||
| @@ -1,11 +1,13 @@ | ||||
| [[case]] # test running a filesystem to exhaustion | ||||
| define.LFS_ERASE_CYCLES = 10 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| define.FILES = 10 | ||||
| code = ''' | ||||
| @@ -40,7 +42,7 @@ code = ''' | ||||
|             if (err == LFS_ERR_NOSPC) { | ||||
|                 goto exhausted; | ||||
|             } | ||||
|         }     | ||||
|         } | ||||
|  | ||||
|         for (uint32_t i = 0; i < FILES; i++) { | ||||
|             // check for errors | ||||
| @@ -57,7 +59,7 @@ code = ''' | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         }     | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         cycle += 1; | ||||
| @@ -70,7 +72,7 @@ exhausted: | ||||
|         // check for errors | ||||
|         sprintf(path, "roadrunner/test%d", i); | ||||
|         lfs_stat(&lfs, path, &info) => 0; | ||||
|     }     | ||||
|     } | ||||
|     lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|     LFS_WARN("completed %d cycles", cycle); | ||||
| @@ -79,12 +81,14 @@ exhausted: | ||||
| [[case]] # test running a filesystem to exhaustion | ||||
|          # which also requires expanding superblocks | ||||
| define.LFS_ERASE_CYCLES = 10 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASEERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_READERROR', | ||||
|     'LFS_TESTBD_BADBLOCK_PROGNOOP', | ||||
|     'LFS_TESTBD_BADBLOCK_ERASENOOP', | ||||
| ] | ||||
| define.FILES = 10 | ||||
| code = ''' | ||||
| @@ -116,7 +120,7 @@ code = ''' | ||||
|             if (err == LFS_ERR_NOSPC) { | ||||
|                 goto exhausted; | ||||
|             } | ||||
|         }     | ||||
|         } | ||||
|  | ||||
|         for (uint32_t i = 0; i < FILES; i++) { | ||||
|             // check for errors | ||||
| @@ -133,7 +137,7 @@ code = ''' | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         }     | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         cycle += 1; | ||||
| @@ -146,7 +150,7 @@ exhausted: | ||||
|         // check for errors | ||||
|         sprintf(path, "test%d", i); | ||||
|         lfs_stat(&lfs, path, &info) => 0; | ||||
|     }     | ||||
|     } | ||||
|     lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|     LFS_WARN("completed %d cycles", cycle); | ||||
| @@ -158,21 +162,11 @@ exhausted: | ||||
| # check for. | ||||
|  | ||||
| [[case]] # wear-level test running a filesystem to exhaustion | ||||
| define.LFS_ERASE_CYCLES = 10 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster | ||||
| define.LFS_ERASE_CYCLES = 20 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
| ] | ||||
| define.FILES = 10 | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
|     lfs_mkdir(&lfs, "roadrunner") => 0; | ||||
|     lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|     uint32_t run_cycles[2]; | ||||
|     const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; | ||||
|  | ||||
| @@ -182,6 +176,11 @@ code = ''' | ||||
|                     (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; | ||||
|         } | ||||
|  | ||||
|         lfs_format(&lfs, &cfg) => 0; | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         lfs_mkdir(&lfs, "roadrunner") => 0; | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         uint32_t cycle = 0; | ||||
|         while (true) { | ||||
|             lfs_mount(&lfs, &cfg) => 0; | ||||
| @@ -208,7 +207,7 @@ code = ''' | ||||
|                 if (err == LFS_ERR_NOSPC) { | ||||
|                     goto exhausted; | ||||
|                 } | ||||
|             }     | ||||
|             } | ||||
|  | ||||
|             for (uint32_t i = 0; i < FILES; i++) { | ||||
|                 // check for errors | ||||
| @@ -225,7 +224,7 @@ code = ''' | ||||
|                 } | ||||
|  | ||||
|                 lfs_file_close(&lfs, &file) => 0; | ||||
|             }     | ||||
|             } | ||||
|             lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|             cycle += 1; | ||||
| @@ -238,7 +237,7 @@ exhausted: | ||||
|             // check for errors | ||||
|             sprintf(path, "roadrunner/test%d", i); | ||||
|             lfs_stat(&lfs, path, &info) => 0; | ||||
|         }     | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         run_cycles[run] = cycle; | ||||
| @@ -247,22 +246,15 @@ exhausted: | ||||
|     } | ||||
|  | ||||
|     // check we increased the lifetime by 2x with ~10% error | ||||
|     LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/10); | ||||
|     LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); | ||||
| ''' | ||||
|  | ||||
| [[case]] # wear-level test + expanding superblock | ||||
| define.LFS_ERASE_CYCLES = 10 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so it runs faster | ||||
| define.LFS_ERASE_CYCLES = 20 | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_BLOCK_CYCLES = 'LFS_ERASE_CYCLES / 2' | ||||
| define.LFS_BADBLOCK_BEHAVIOR = [ | ||||
|     'LFS_TESTBD_BADBLOCK_NOPROG', | ||||
|     'LFS_TESTBD_BADBLOCK_NOERASE', | ||||
|     'LFS_TESTBD_BADBLOCK_NOREAD', | ||||
| ] | ||||
| define.FILES = 10 | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|  | ||||
|     uint32_t run_cycles[2]; | ||||
|     const uint32_t run_block_count[2] = {LFS_BLOCK_COUNT/2, LFS_BLOCK_COUNT}; | ||||
|  | ||||
| @@ -272,6 +264,8 @@ code = ''' | ||||
|                     (b < run_block_count[run]) ? 0 : LFS_ERASE_CYCLES) => 0; | ||||
|         } | ||||
|  | ||||
|         lfs_format(&lfs, &cfg) => 0; | ||||
|  | ||||
|         uint32_t cycle = 0; | ||||
|         while (true) { | ||||
|             lfs_mount(&lfs, &cfg) => 0; | ||||
| @@ -298,7 +292,7 @@ code = ''' | ||||
|                 if (err == LFS_ERR_NOSPC) { | ||||
|                     goto exhausted; | ||||
|                 } | ||||
|             }     | ||||
|             } | ||||
|  | ||||
|             for (uint32_t i = 0; i < FILES; i++) { | ||||
|                 // check for errors | ||||
| @@ -315,7 +309,7 @@ code = ''' | ||||
|                 } | ||||
|  | ||||
|                 lfs_file_close(&lfs, &file) => 0; | ||||
|             }     | ||||
|             } | ||||
|             lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|             cycle += 1; | ||||
| @@ -328,7 +322,7 @@ exhausted: | ||||
|             // check for errors | ||||
|             sprintf(path, "test%d", i); | ||||
|             lfs_stat(&lfs, path, &info) => 0; | ||||
|         }     | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         run_cycles[run] = cycle; | ||||
| @@ -337,5 +331,115 @@ exhausted: | ||||
|     } | ||||
|  | ||||
|     // check we increased the lifetime by 2x with ~10% error | ||||
|     LFS_ASSERT(run_cycles[1] > 2*run_cycles[0]-run_cycles[0]/10); | ||||
|     LFS_ASSERT(run_cycles[1]*110/100 > 2*run_cycles[0]); | ||||
| ''' | ||||
|  | ||||
| [[case]] # test that we wear blocks roughly evenly | ||||
| define.LFS_ERASE_CYCLES = 0xffffffff | ||||
| define.LFS_BLOCK_COUNT = 256 # small bd so test runs faster | ||||
| define.LFS_BLOCK_CYCLES = [5, 4, 3, 2, 1] | ||||
| #define.LFS_BLOCK_CYCLES = [4, 2] | ||||
| define.CYCLES = 100 | ||||
| define.FILES = 10 | ||||
| code = ''' | ||||
|     lfs_format(&lfs, &cfg) => 0; | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
|     lfs_mkdir(&lfs, "roadrunner") => 0; | ||||
|     lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|     uint32_t cycle = 0; | ||||
|     while (cycle < CYCLES) { | ||||
|         lfs_mount(&lfs, &cfg) => 0; | ||||
|         for (uint32_t i = 0; i < FILES; i++) { | ||||
|             // chose name, roughly random seed, and random 2^n size | ||||
|             sprintf(path, "roadrunner/test%d", i); | ||||
|             srand(cycle * i); | ||||
|             size = 1 << 4; //((rand() % 10)+2); | ||||
|  | ||||
|             lfs_file_open(&lfs, &file, path, | ||||
|                     LFS_O_WRONLY | LFS_O_CREAT | LFS_O_TRUNC) => 0; | ||||
|  | ||||
|             for (lfs_size_t j = 0; j < size; j++) { | ||||
|                 char c = 'a' + (rand() % 26); | ||||
|                 lfs_ssize_t res = lfs_file_write(&lfs, &file, &c, 1); | ||||
|                 assert(res == 1 || res == LFS_ERR_NOSPC); | ||||
|                 if (res == LFS_ERR_NOSPC) { | ||||
|                     goto exhausted; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             err = lfs_file_close(&lfs, &file); | ||||
|             assert(err == 0 || err == LFS_ERR_NOSPC); | ||||
|             if (err == LFS_ERR_NOSPC) { | ||||
|                 goto exhausted; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         for (uint32_t i = 0; i < FILES; i++) { | ||||
|             // check for errors | ||||
|             sprintf(path, "roadrunner/test%d", i); | ||||
|             srand(cycle * i); | ||||
|             size = 1 << 4; //((rand() % 10)+2); | ||||
|  | ||||
|             lfs_file_open(&lfs, &file, path, LFS_O_RDONLY) => 0; | ||||
|             for (lfs_size_t j = 0; j < size; j++) { | ||||
|                 char c = 'a' + (rand() % 26); | ||||
|                 char r; | ||||
|                 lfs_file_read(&lfs, &file, &r, 1) => 1; | ||||
|                 assert(r == c); | ||||
|             } | ||||
|  | ||||
|             lfs_file_close(&lfs, &file) => 0; | ||||
|         } | ||||
|         lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|         cycle += 1; | ||||
|     } | ||||
|  | ||||
| exhausted: | ||||
|     // should still be readable | ||||
|     lfs_mount(&lfs, &cfg) => 0; | ||||
|     for (uint32_t i = 0; i < FILES; i++) { | ||||
|         // check for errors | ||||
|         sprintf(path, "roadrunner/test%d", i); | ||||
|         lfs_stat(&lfs, path, &info) => 0; | ||||
|     } | ||||
|     lfs_unmount(&lfs) => 0; | ||||
|  | ||||
|     LFS_WARN("completed %d cycles", cycle); | ||||
|  | ||||
|     // check the wear on our block device | ||||
|     lfs_testbd_wear_t minwear = -1; | ||||
|     lfs_testbd_wear_t totalwear = 0; | ||||
|     lfs_testbd_wear_t maxwear = 0; | ||||
|     // skip 0 and 1 as superblock movement is intentionally avoided | ||||
|     for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { | ||||
|         lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); | ||||
|         printf("%08x: wear %d\n", b, wear); | ||||
|         assert(wear >= 0); | ||||
|         if (wear < minwear) { | ||||
|             minwear = wear; | ||||
|         } | ||||
|         if (wear > maxwear) { | ||||
|             maxwear = wear; | ||||
|         } | ||||
|         totalwear += wear; | ||||
|     } | ||||
|     lfs_testbd_wear_t avgwear = totalwear / LFS_BLOCK_COUNT; | ||||
|     LFS_WARN("max wear: %d cycles", maxwear); | ||||
|     LFS_WARN("avg wear: %d cycles", totalwear / LFS_BLOCK_COUNT); | ||||
|     LFS_WARN("min wear: %d cycles", minwear); | ||||
|  | ||||
|     // find standard deviation^2 | ||||
|     lfs_testbd_wear_t dev2 = 0; | ||||
|     for (lfs_block_t b = 2; b < LFS_BLOCK_COUNT; b++) { | ||||
|         lfs_testbd_wear_t wear = lfs_testbd_getwear(&cfg, b); | ||||
|         assert(wear >= 0); | ||||
|         lfs_testbd_swear_t diff = wear - avgwear; | ||||
|         dev2 += diff*diff; | ||||
|     } | ||||
|     dev2 /= totalwear; | ||||
|     LFS_WARN("std dev^2: %d", dev2); | ||||
|     assert(dev2 < 8); | ||||
| ''' | ||||
|  | ||||
|   | ||||
| @@ -57,10 +57,12 @@ code = ''' | ||||
|  | ||||
| [[case]] # reentrant testing for orphans, basically just spam mkdir/remove | ||||
| reentrant = true | ||||
| # TODO fix this case, caused by non-DAG trees | ||||
| if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' | ||||
| define = [ | ||||
|     {FILES=6,  DEPTH=1, CYCLES=50}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=50}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=50}, | ||||
|     {FILES=6,  DEPTH=1, CYCLES=20}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=20}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=20}, | ||||
| ] | ||||
| code = ''' | ||||
|     err = lfs_mount(&lfs, &cfg); | ||||
|   | ||||
| @@ -147,10 +147,12 @@ code = ''' | ||||
|          # orphan testing, except here we also set block_cycles so that | ||||
|          # almost every tree operation needs a relocation | ||||
| reentrant = true | ||||
| # TODO fix this case, caused by non-DAG trees | ||||
| if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' | ||||
| define = [ | ||||
|     {FILES=6,  DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=6,  DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
| ] | ||||
| code = ''' | ||||
|     err = lfs_mount(&lfs, &cfg); | ||||
| @@ -207,10 +209,12 @@ code = ''' | ||||
|  | ||||
| [[case]] # reentrant testing for relocations, but now with random renames! | ||||
| reentrant = true | ||||
| # TODO fix this case, caused by non-DAG trees | ||||
| if = '!(DEPTH == 3 && LFS_CACHE_SIZE != 64)' | ||||
| define = [ | ||||
|     {FILES=6,  DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=50, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=6,  DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=26, DEPTH=1, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
|     {FILES=3,  DEPTH=3, CYCLES=20, LFS_BLOCK_CYCLES=1}, | ||||
| ] | ||||
| code = ''' | ||||
|     err = lfs_mount(&lfs, &cfg); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user