mirror of
				https://github.com/eledio-devices/thirdparty-littlefs.git
				synced 2025-10-31 16:14:16 +01:00 
			
		
		
		
	WIP added wip journalling things for dirs
This commit is contained in:
		
							
								
								
									
										901
									
								
								lfs.c
									
									
									
									
									
								
							
							
						
						
									
										901
									
								
								lfs.c
									
									
									
									
									
								
							| @@ -221,9 +221,7 @@ static int lfs_cache_prog(lfs_t *lfs, lfs_cache_t *pcache, | ||||
| /// General lfs block device operations /// | ||||
| static int lfs_bd_read(lfs_t *lfs, lfs_block_t block, | ||||
|         lfs_off_t off, void *buffer, lfs_size_t size) { | ||||
|     // if we ever do more than writes to alternating pairs, | ||||
|     // this may need to consider pcache | ||||
|     return lfs_cache_read(lfs, &lfs->rcache, NULL, | ||||
|     return lfs_cache_read(lfs, &lfs->rcache, &lfs->pcache, | ||||
|             block, off, buffer, size); | ||||
| } | ||||
|  | ||||
| @@ -427,6 +425,903 @@ static inline bool lfs_pairsync( | ||||
|            (paira[0] == pairb[1] && paira[1] == pairb[0]); | ||||
| } | ||||
|  | ||||
| enum { | ||||
|     LFS_TAG_VALID = (int)0x80000000, | ||||
|     LFS_TAG_TYPE  = (int)0x7fc00000, | ||||
|     LFS_TAG_ID    = (int)0x001ff000, | ||||
|     LFS_TAG_SIZE  = (int)0x00000fff, | ||||
| }; | ||||
|  | ||||
| static inline uint32_t lfs_mktag(uint16_t type, uint16_t id, uint16_t size) { | ||||
|     return (type << 22) | (id << 12) | size; | ||||
| } | ||||
|  | ||||
| static inline bool lfs_tag_valid(uint32_t tag) { | ||||
|     return !(tag & 0x80000000); | ||||
| } | ||||
|  | ||||
| static inline uint32_t lfs_tag_uid(uint32_t tag) { | ||||
|     return (tag & 0x7fdff000) >> 12; | ||||
| } | ||||
|  | ||||
| static inline uint16_t lfs_tag_type(uint32_t tag) { | ||||
|     return (tag & 0x7fc00000) >> 22; | ||||
| } | ||||
|  | ||||
| static inline uint16_t lfs_tag_id(uint32_t tag) { | ||||
|     return (tag & 0x001ff000) >> 12; | ||||
| } | ||||
|  | ||||
| static uint16_t lfs_tag_size(uint32_t tag) { | ||||
|     return tag & 0x00000fff; | ||||
| } | ||||
|  | ||||
| struct lfs_region__ { | ||||
|     uint32_t tag; | ||||
|     union { | ||||
|         void *buffer; | ||||
|         struct { | ||||
|             lfs_block_t block; | ||||
|             lfs_off_t off; | ||||
|         } d; | ||||
|     } u; | ||||
| }; | ||||
|  | ||||
| struct lfs_commit { | ||||
|     lfs_block_t block; | ||||
|     lfs_off_t off; | ||||
|     lfs_off_t begin; | ||||
|     lfs_off_t end; | ||||
|  | ||||
|     uint32_t ptag; | ||||
|     uint32_t crc; | ||||
|  | ||||
|     struct { | ||||
|         int16_t id; | ||||
|         uint16_t type; | ||||
|     } compact; | ||||
| }; | ||||
|  | ||||
| static int lfs_commit_traverse(lfs_t *lfs, struct lfs_commit *commit, | ||||
|         int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region), | ||||
|         void *data) { | ||||
|     // iterate over dir block backwards (for faster lookups) | ||||
|     lfs_block_t block = commit->block; | ||||
|     lfs_off_t off = commit->off; | ||||
|     uint32_t tag = commit->ptag; | ||||
|  | ||||
|     while (off != sizeof(uint32_t)) { | ||||
|         //printf("read %#010x at %x:%x\n", tag, block, off); | ||||
|         int err = cb(lfs, data, (struct lfs_region__){ | ||||
|                 .tag=(0x80000000 | tag), | ||||
|                 .u.d.block=block, | ||||
|                 .u.d.off=off-lfs_tag_size(tag)}); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         LFS_ASSERT(off > sizeof(uint32_t)+lfs_tag_size(tag)); | ||||
|         off -= sizeof(uint32_t)+lfs_tag_size(tag); | ||||
|  | ||||
|         uint32_t ntag; | ||||
|         err = lfs_bd_read(lfs, block, off, &ntag, sizeof(ntag)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         tag ^= lfs_fromle32(ntag); | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int lfs_commit_compactcheck(lfs_t *lfs, void *p, | ||||
|         struct lfs_region__ region) { | ||||
|     struct lfs_commit *commit = p; | ||||
|     if (lfs_tag_id(region.tag) != commit->compact.id) { | ||||
|         return 1; | ||||
|     } else if (lfs_tag_type(region.tag) == commit->compact.type) { | ||||
|         return 2; | ||||
|     } | ||||
|      | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int lfs_commit_commit(lfs_t *lfs, | ||||
|         struct lfs_commit *commit, struct lfs_region__ region) { | ||||
|     // request for compaction? | ||||
|     if (commit->compact.id >= 0) { | ||||
|         if (lfs_tag_id(region.tag) != commit->compact.id) { | ||||
|             // ignore non-matching ids | ||||
|             return 0; | ||||
|         } | ||||
|  | ||||
|         commit->compact.type = lfs_tag_type(region.tag); | ||||
|         int res = lfs_commit_traverse(lfs, commit, | ||||
|                 lfs_commit_compactcheck, commit); | ||||
|         //printf("traverse(%d, %#010x) -> %d\n", commit->compact.id, region.tag, res); | ||||
|         if (res < 0) { | ||||
|             return res; | ||||
|         } | ||||
|  | ||||
|         if (res == 2) { | ||||
|             //printf("ignoring %#010x at %x:%x\n", region.tag, commit->block, commit->off); | ||||
|             // already committed | ||||
|             return 0; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // check if we fit | ||||
|     lfs_size_t size = lfs_tag_size(region.tag); | ||||
|     //printf("writing %#010x at %x:%x\n", region.tag, commit->block, commit->off); | ||||
|     if (commit->off + sizeof(uint32_t)+size > commit->end) { | ||||
|         return LFS_ERR_NOSPC; | ||||
|     } | ||||
|  | ||||
|     // write out tag | ||||
|     uint32_t tag = lfs_tole32((region.tag & 0x7fffffff) ^ commit->ptag); | ||||
|     lfs_crc(&commit->crc, &tag, sizeof(tag)); | ||||
|     int err = lfs_bd_prog(lfs, commit->block, commit->off, &tag, sizeof(tag)); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|     commit->off += sizeof(tag); | ||||
|  | ||||
|     if (!(region.tag & 0x80000000)) { | ||||
|         // from memory | ||||
|         lfs_crc(&commit->crc, region.u.buffer, size); | ||||
|         err = lfs_bd_prog(lfs, commit->block, commit->off, | ||||
|                 region.u.buffer, size); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } else { | ||||
|         // from disk | ||||
|         for (lfs_off_t i = 0; i < size; i++) { | ||||
|             uint8_t dat; | ||||
|             int err = lfs_bd_read(lfs, | ||||
|                     region.u.d.block, region.u.d.off+i, &dat, 1); | ||||
|             if (err) { | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             lfs_crc(&commit->crc, &dat, 1); | ||||
|             err = lfs_bd_prog(lfs, commit->block, commit->off+i, &dat, 1); | ||||
|             if (err) { | ||||
|                 return err; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     commit->off += size; | ||||
|     commit->ptag = region.tag; | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| static int lfs_commit_crc(lfs_t *lfs, struct lfs_commit *commit) { | ||||
|     // align to program units | ||||
|     lfs_off_t noff = lfs_alignup( | ||||
|             commit->off + 2*sizeof(uint32_t), lfs->cfg->prog_size); | ||||
|  | ||||
|     // read erased state from next program unit | ||||
|     uint32_t tag; | ||||
|     int err = lfs_bd_read(lfs, commit->block, noff, &tag, sizeof(tag)); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     // build crc tag | ||||
|     tag = (0x80000000 & ~lfs_fromle32(tag)) | | ||||
|             lfs_mktag(LFS_TYPE_CRC_, 0x1ff, | ||||
|                 noff - (commit->off+sizeof(uint32_t))); | ||||
|  | ||||
|     // write out crc | ||||
|     uint32_t footer[2]; | ||||
|     footer[0] = lfs_tole32(tag ^ commit->ptag); | ||||
|     lfs_crc(&commit->crc, &footer[0], sizeof(footer[0])); | ||||
|     footer[1] = lfs_tole32(commit->crc); | ||||
|     err = lfs_bd_prog(lfs, commit->block, commit->off, | ||||
|             footer, sizeof(footer)); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|     commit->off += sizeof(uint32_t)+lfs_tag_size(tag); | ||||
|     commit->ptag = tag; | ||||
|  | ||||
|     // flush buffers | ||||
|     err = lfs_bd_sync(lfs); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     // successful commit, check checksum to make sure | ||||
|     uint32_t crc = 0xffffffff; | ||||
|     err = lfs_bd_crc(lfs, commit->block, commit->begin, | ||||
|             commit->off-lfs_tag_size(tag) - commit->begin, &crc); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     if (crc != commit->crc) { | ||||
|         return LFS_ERR_CORRUPT; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_alloc_(lfs_t *lfs, lfs_dir_t_ *dir) { | ||||
|     // allocate pair of dir blocks (backwards, so we write to block 1 first) | ||||
|     for (int i = 0; i < 2; i++) { | ||||
|         int err = lfs_alloc(lfs, &dir->pair[(i+1)%2]); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // rather than clobbering one of the blocks we just pretend | ||||
|     // the revision may be valid | ||||
|     int err = lfs_bd_read(lfs, dir->pair[0], 0, &dir->rev, 4); | ||||
|     dir->rev = lfs_fromle32(dir->rev); | ||||
|     if (err) { | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     // set defaults | ||||
|     dir->off = sizeof(dir->rev); | ||||
|     dir->etag = 0; | ||||
|     dir->count = 0; | ||||
|     dir->erased = false; | ||||
|     dir->tail[0] = 0xffffffff; | ||||
|     dir->tail[1] = 0xffffffff; | ||||
|  | ||||
|     // don't write out yet, let caller take care of that | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_fetch_(lfs_t *lfs, | ||||
|         lfs_dir_t_ *dir, const lfs_block_t pair[2], | ||||
|         int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region), | ||||
|         void *data) { | ||||
|     dir->pair[0] = pair[0]; | ||||
|     dir->pair[1] = pair[1]; | ||||
|  | ||||
|     // find the block with the most recent revision | ||||
|     uint32_t rev[2]; | ||||
|     for (int i = 0; i < 2; i++) { | ||||
|         int err = lfs_bd_read(lfs, dir->pair[i], 0, &rev[i], sizeof(rev[i])); | ||||
|         rev[i] = lfs_fromle32(rev[i]); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (lfs_scmp(rev[1], rev[0]) > 0) { | ||||
|         lfs_pairswap(dir->pair); | ||||
|         lfs_pairswap(rev); | ||||
|     } | ||||
|  | ||||
|     // load blocks and check crc | ||||
|     for (int i = 0; i < 2; i++) { | ||||
|         lfs_off_t off = sizeof(dir->rev); | ||||
|         uint32_t ptag = 0; | ||||
|         uint32_t crc = 0xffffffff; | ||||
|  | ||||
|         dir->rev = lfs_tole32(rev[0]); | ||||
|         lfs_crc(&crc, &dir->rev, sizeof(dir->rev)); | ||||
|         dir->rev = lfs_fromle32(dir->rev); | ||||
|  | ||||
|         while (true) { | ||||
|             // extract next tag | ||||
|             uint32_t tag; | ||||
|             int err = lfs_bd_read(lfs, dir->pair[0], off, &tag, sizeof(tag)); | ||||
|             if (err) { | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             lfs_crc(&crc, &tag, sizeof(tag)); | ||||
|             tag = lfs_fromle32(tag) ^ ptag; | ||||
|             printf("tag %#010x (%x:%x %#010x)\n", tag, dir->pair[0], off, lfs_tag_type(ptag)); | ||||
|  | ||||
|             // next commit not yet programmed | ||||
|             if (lfs_tag_type(ptag) == LFS_TYPE_CRC_ && lfs_tag_valid(tag)) { | ||||
|                 dir->erased = true; | ||||
|                 return 0; | ||||
|             } | ||||
|  | ||||
|             // check we're in valid range | ||||
|             if (off + sizeof(uint32_t)+lfs_tag_size(tag) > | ||||
|                     lfs->cfg->block_size - 2*sizeof(uint32_t)) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (lfs_tag_type(tag) == LFS_TYPE_CRC_) { | ||||
|                 // check the crc entry | ||||
|                 uint32_t dcrc; | ||||
|                 int err = lfs_bd_read(lfs, dir->pair[0], | ||||
|                         off+sizeof(uint32_t), &dcrc, sizeof(dcrc)); | ||||
|                 if (err) { | ||||
|                     return err; | ||||
|                 } | ||||
|  | ||||
|                 if (crc != lfs_fromle32(dcrc)) { | ||||
|                     if (off == sizeof(dir->rev)) { | ||||
|                         // try other block | ||||
|                         break; | ||||
|                     } else { | ||||
|                         // consider what we have good enough | ||||
|                         dir->erased = false; | ||||
|                         return 0; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 dir->off = off + sizeof(uint32_t)+lfs_tag_size(tag); | ||||
|                 dir->etag = tag; | ||||
|                 crc = 0xffffffff; | ||||
|             } else { | ||||
|                 err = lfs_bd_crc(lfs, dir->pair[0], | ||||
|                         off+sizeof(uint32_t), lfs_tag_size(tag), &crc); | ||||
|                 if (err) { | ||||
|                     return err; | ||||
|                 } | ||||
|  | ||||
|                 if (cb) { | ||||
|                     err = cb(lfs, data, (struct lfs_region__){ | ||||
|                             .tag=(tag | 0x80000000), | ||||
|                             .u.d.block=dir->pair[0], | ||||
|                             .u.d.off=off+sizeof(uint32_t)}); | ||||
|                     if (err) { | ||||
|                         return err; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             ptag = tag; | ||||
|             off += sizeof(uint32_t)+lfs_tag_size(tag); | ||||
|         } | ||||
|  | ||||
|         // failed, try the other crc? | ||||
|         lfs_pairswap(dir->pair); | ||||
|         lfs_pairswap(rev); | ||||
|     } | ||||
|  | ||||
|     LFS_ERROR("Corrupted dir pair at %d %d", dir->pair[0], dir->pair[1]); | ||||
|     return LFS_ERR_CORRUPT; | ||||
| } | ||||
|  | ||||
| static int lfs_dir_traverse2_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         int (*cb)(lfs_t *lfs, void *data, struct lfs_region__ region), | ||||
|         void *data) { | ||||
|     return lfs_commit_traverse(lfs, &(struct lfs_commit){ | ||||
|                 .block=dir->pair[0], .off=dir->off, .ptag=dir->etag}, | ||||
|             cb, data); | ||||
| } | ||||
|  | ||||
| struct lfs_dir_getter { | ||||
|     uint32_t tag; | ||||
|     uint32_t mask; | ||||
|     void *buffer; | ||||
| }; | ||||
|  | ||||
| static int lfs_dir_getter(lfs_t *lfs, void *p, struct lfs_region__ region) { | ||||
|     struct lfs_dir_getter *getter = p; | ||||
|     if ((region.tag & getter->mask) == (getter->tag & getter->mask)) { | ||||
|         lfs_size_t size = lfs_tag_size(getter->tag); | ||||
|         if (lfs_tag_size(region.tag) > size) { | ||||
|             return LFS_ERR_RANGE; | ||||
|         } | ||||
|  | ||||
|         int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off, | ||||
|                 getter->buffer, size); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         memset((uint8_t*)getter->buffer + size, 0, | ||||
|                 lfs_tag_size(region.tag) - size); | ||||
|         getter->tag |= region.tag & ~0x80000fff; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| /*static*/ int32_t lfs_dir_get_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         uint32_t tag, uint32_t mask, void *buffer) { | ||||
|     struct lfs_dir_getter getter = {tag, mask, buffer}; | ||||
|     int res = lfs_dir_traverse2_(lfs, dir, lfs_dir_getter, &getter); | ||||
|     if (res < 0) { | ||||
|         return res; | ||||
|     } | ||||
|  | ||||
|     if (!res) { | ||||
|         return LFS_ERR_NOENT; | ||||
|     } | ||||
|  | ||||
|     return getter.tag; | ||||
| } | ||||
|  | ||||
| struct lfs_dir_mover { | ||||
|     // traversal things | ||||
|     lfs_dir_t_ *dir; | ||||
|     int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit); | ||||
|     void *data; | ||||
|  | ||||
|     // ids to iterate through | ||||
|     uint16_t begin; | ||||
|     uint16_t end; | ||||
|     uint16_t ack; | ||||
| }; | ||||
|  | ||||
| static int lfs_dir_mover_commit(lfs_t *lfs, void *p, | ||||
|         struct lfs_region__ region) { | ||||
|     return lfs_commit_commit(lfs, p, region); | ||||
| } | ||||
|  | ||||
| int lfs_dir_mover(lfs_t *lfs, void *p, struct lfs_commit *commit) { | ||||
|     struct lfs_dir_mover *mover = p; | ||||
|     for (int i = mover->begin; i < mover->end; i++) { | ||||
|         // tell the committer to check for duplicates | ||||
|         uint16_t old = commit->compact.id; | ||||
|         if (commit->compact.id < 0) { | ||||
|             commit->compact.id = i; | ||||
|         } | ||||
|  | ||||
|         // commit pending commits | ||||
|         int err = mover->cb(lfs, mover->data, commit); | ||||
|         if (err) { | ||||
|             commit->compact.id = old; | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         // iterate over on-disk regions | ||||
|         err = lfs_dir_traverse2_(lfs, mover->dir, | ||||
|                 lfs_dir_mover_commit, commit); | ||||
|         if (err) { | ||||
|             commit->compact.id = old; | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         mover->ack = i; | ||||
|         commit->compact.id = old; | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_compact2_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit), | ||||
|         void *data) { | ||||
|     // save some state in case block is bad | ||||
|     const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]}; | ||||
|     bool relocated = false; | ||||
|  | ||||
|     // increment revision count | ||||
|     dir->rev += 1; | ||||
|  | ||||
|     while (true) { | ||||
|         // setup mover | ||||
|         struct lfs_dir_mover mover = { | ||||
|             .dir = dir, | ||||
|             .cb = cb, | ||||
|             .data = data, | ||||
|  | ||||
|             .begin = 0, | ||||
|             .end = dir->count, | ||||
|             .ack = 0, | ||||
|         }; | ||||
|  | ||||
|         if (true) { | ||||
|             // erase block to write to | ||||
|             int err = lfs_bd_erase(lfs, dir->pair[1]); | ||||
|             if (err) { | ||||
|                 if (err == LFS_ERR_CORRUPT) { | ||||
|                     goto relocate; | ||||
|                 } | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             // write out header | ||||
|             uint32_t crc = 0xffffffff; | ||||
|             uint32_t rev = lfs_tole32(dir->rev); | ||||
|             lfs_crc(&crc, &rev, sizeof(rev)); | ||||
|             err = lfs_bd_prog(lfs, dir->pair[1], 0, &rev, sizeof(rev)); | ||||
|             if (err) { | ||||
|                 if (err == LFS_ERR_CORRUPT) { | ||||
|                     goto relocate; | ||||
|                 } | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             // setup compaction | ||||
|             struct lfs_commit commit = { | ||||
|                 .block = dir->pair[1], | ||||
|                 .off = sizeof(dir->rev), | ||||
|                 // leave space for tail pointer | ||||
|                 .begin = 0, | ||||
|                 .end = lfs_min(lfs->cfg->block_size - 5*sizeof(uint32_t), | ||||
|                         lfs_alignup(lfs->cfg->block_size / 2, | ||||
|                             lfs->cfg->prog_size)), | ||||
|                 .crc = crc, | ||||
|                 .ptag = 0, | ||||
|                 .compact.id = -1, | ||||
|             }; | ||||
|  | ||||
|             // run compaction over mover | ||||
|             err = lfs_dir_mover(lfs, &mover, &commit); | ||||
|             if (err) { | ||||
|                 if (err == LFS_ERR_NOSPC) { | ||||
|                     goto split; | ||||
|                 } else if (err == LFS_ERR_CORRUPT) { | ||||
|                     goto relocate; | ||||
|                 } | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             if (!lfs_pairisnull(dir->tail)) { | ||||
|                 // TODO le32 | ||||
|                 commit.end = lfs->cfg->block_size - 2*sizeof(uint32_t), | ||||
|                 err = lfs_commit_commit(lfs, &commit, (struct lfs_region__){ | ||||
|                         .tag=lfs_mktag(LFS_TYPE_TAIL_, 0x1ff, | ||||
|                             sizeof(dir->tail)), | ||||
|                         .u.buffer=dir->tail}); | ||||
|                 if (err) { | ||||
|                     if (err == LFS_ERR_CORRUPT) { | ||||
|                         goto relocate; | ||||
|                     } | ||||
|                     return err; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             err = lfs_commit_crc(lfs, &commit); | ||||
|             if (err) { | ||||
|                 if (err == LFS_ERR_CORRUPT) { | ||||
|                     goto relocate; | ||||
|                 } | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             // successful compaction, swap dir pair to indicate most recent | ||||
|             lfs_pairswap(dir->pair); | ||||
|             dir->off = commit.off; | ||||
|             dir->etag = commit.ptag; | ||||
|             dir->erased = true; | ||||
|         } | ||||
|         break; | ||||
|  | ||||
| split: | ||||
|         // commit no longer fits, need to split dir | ||||
|         dir->count = mover.ack; | ||||
|         mover.begin = mover.ack+1; | ||||
|  | ||||
|         // drop caches and create tail | ||||
|         lfs->pcache.block = 0xffffffff; | ||||
|  | ||||
|         lfs_dir_t_ tail; | ||||
|         int err = lfs_dir_alloc_(lfs, &tail); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         tail.tail[0] = dir->tail[0]; | ||||
|         tail.tail[1] = dir->tail[1]; | ||||
|  | ||||
|         err = lfs_dir_compact2_(lfs, &tail, lfs_dir_mover, &mover); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         dir->tail[0] = tail.pair[0]; | ||||
|         dir->tail[1] = tail.pair[1]; | ||||
|         continue; | ||||
|  | ||||
| relocate: | ||||
|         //commit was corrupted | ||||
|         LFS_DEBUG("Bad block at %d", dir->pair[1]); | ||||
|  | ||||
|         // drop caches and prepare to relocate block | ||||
|         relocated = true; | ||||
|         lfs->pcache.block = 0xffffffff; | ||||
|  | ||||
|         // can't relocate superblock, filesystem is now frozen | ||||
|         if (lfs_paircmp(oldpair, (const lfs_block_t[2]){0, 1}) == 0) { | ||||
|             LFS_WARN("Superblock %d has become unwritable", oldpair[1]); | ||||
|             return LFS_ERR_CORRUPT; | ||||
|         } | ||||
|  | ||||
|         // relocate half of pair | ||||
|         err = lfs_alloc(lfs, &dir->pair[1]); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|  | ||||
|         continue; | ||||
|     } | ||||
|  | ||||
|     if (relocated) { | ||||
|         // update references if we relocated | ||||
|         LFS_DEBUG("Relocating %d %d to %d %d", | ||||
|                 oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]); | ||||
|         int err = lfs_relocate(lfs, oldpair, dir->pair); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // shift over any directories that are affected | ||||
|     for (lfs_dir_t *d = lfs->dirs; d; d = d->next) { | ||||
|         if (lfs_paircmp(d->pair, dir->pair) == 0) { | ||||
|             d->pair[0] = dir->pair[0]; | ||||
|             d->pair[1] = dir->pair[1]; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_commit2_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         int (*cb)(lfs_t *lfs, void *data, struct lfs_commit *commit), | ||||
|         void *data) { | ||||
|     if (!dir->erased) { | ||||
|         // not erased, must compact | ||||
|         return lfs_dir_compact2_(lfs, dir, cb, data); | ||||
|     } | ||||
|  | ||||
|     struct lfs_commit commit = { | ||||
|         .block = dir->pair[0], | ||||
|         .begin = dir->off, | ||||
|         .off = dir->off, | ||||
|         .end = lfs->cfg->block_size - 2*sizeof(uint32_t), | ||||
|         .crc = 0xffffffff, | ||||
|         .ptag = dir->etag, | ||||
|         .compact.id = -1, | ||||
|     }; | ||||
|  | ||||
|     int err = cb(lfs, data, &commit); | ||||
|     if (err) { | ||||
|         if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { | ||||
|             return lfs_dir_compact2_(lfs, dir, cb, data); | ||||
|         } | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     err = lfs_commit_crc(lfs, &commit); | ||||
|     if (err) { | ||||
|         if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) { | ||||
|             return lfs_dir_compact2_(lfs, dir, cb, data); | ||||
|         } | ||||
|         return err; | ||||
|     } | ||||
|  | ||||
|     // successful commit, lets update dir | ||||
|     dir->off = commit.off; | ||||
|     dir->etag = commit.ptag; | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| struct lfs_dir_commit_regions { | ||||
|     const struct lfs_region__ *regions; | ||||
|     int count; | ||||
| }; | ||||
|  | ||||
| int lfs_dir_commit_regions(lfs_t *lfs, void *p, struct lfs_commit *commit) { | ||||
|     struct lfs_dir_commit_regions *region = p; | ||||
|     for (int i = 0; i < region->count; i++) { | ||||
|         int err = lfs_commit_commit(lfs, commit, region->regions[i]); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| // TODO rm me, just for testing | ||||
| /*static*/ int lfs_dir_compact_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         const struct lfs_region__ *regions, int count) { | ||||
|     return lfs_dir_compact2_(lfs, dir, lfs_dir_commit_regions, | ||||
|         &(struct lfs_dir_commit_regions){regions, count}); | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_commit_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         const struct lfs_region__ *regions, int count) { | ||||
|     return lfs_dir_commit2_(lfs, dir, lfs_dir_commit_regions, | ||||
|         &(struct lfs_dir_commit_regions){regions, count}); | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_add(lfs_t *lfs, lfs_dir_t_ *dir) { | ||||
|     uint16_t id = dir->count; | ||||
|     dir->count += 1; | ||||
|     return id; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_drop(lfs_t *lfs, lfs_dir_t_ *dir, uint16_t id) { | ||||
|     dir->count -= 1; | ||||
|     // TODO compact during traverse when compacting? | ||||
|     return lfs_dir_commit_(lfs, dir, (struct lfs_region__[]){{ | ||||
|             lfs_mktag(LFS_TYPE_DROP_, id, 0)}}, 1); | ||||
| } | ||||
|  | ||||
| struct lfs_dir_setter { | ||||
|     const struct lfs_region__ *regions; | ||||
|     int count; | ||||
| }; | ||||
|  | ||||
| int lfs_dir_setter(lfs_t *lfs, void *p, struct lfs_commit *commit) { | ||||
|     struct lfs_dir_setter *setter = p; | ||||
|     for (int i = 0; i < setter->count; i++) { | ||||
|         int err = lfs_commit_commit(lfs, commit, setter->regions[i]); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int lfs_dir_set_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         const struct lfs_region__ *regions, int count) { | ||||
|     return lfs_dir_commit2_(lfs, dir, lfs_dir_setter, | ||||
|         &(struct lfs_dir_setter){regions, count}); | ||||
| } | ||||
|  | ||||
| struct lfs_dir_lookuper { | ||||
|     const char *name; | ||||
|     lfs_size_t size; | ||||
|     lfs_block_t pair[2]; | ||||
|     uint32_t tag; | ||||
|     lfs_block_t tail[2]; | ||||
| }; | ||||
|  | ||||
| static int lfs_dir_lookuper(lfs_t *lfs, void *p, struct lfs_region__ region) { | ||||
|     struct lfs_dir_lookuper *lookup = p; | ||||
|  | ||||
|     if (lfs_tag_type(region.tag) == LFS_TYPE_NAME_ && | ||||
|             lfs_tag_size(region.tag) == lookup->size) { | ||||
|         int res = lfs_bd_cmp(lfs, region.u.d.block, region.u.d.off, | ||||
|                 lookup->name, lookup->size); | ||||
|         if (res < 0) { | ||||
|             return res; | ||||
|         } | ||||
|  | ||||
|         if (res) { | ||||
|             // found a match | ||||
|             lookup->tag = region.tag & ~0x80000000; | ||||
|             lookup->pair[0] = 0xffffffff; | ||||
|             lookup->pair[1] = 0xffffffff; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (lfs_tag_valid(lookup->tag) && | ||||
|             lfs_tag_id(region.tag) == lfs_tag_id(lookup->tag) && | ||||
|             lfs_tag_type(region.tag) == LFS_TYPE_DIR_) { | ||||
|         int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off, | ||||
|                 lookup->pair, sizeof(lookup->pair)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     if (lfs_tag_type(lookup->tag) == LFS_TYPE_TAIL_) { | ||||
|         int err = lfs_bd_read(lfs, region.u.d.block, region.u.d.off, | ||||
|                 lookup->tail, sizeof(lookup->tail)); | ||||
|         if (err) { | ||||
|             return err; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| /*static*/ int32_t lfs_dir_lookup_(lfs_t *lfs, lfs_dir_t_ *dir, | ||||
|         const char **path) { | ||||
|     struct lfs_dir_lookuper lookup = { | ||||
|         .name = *path, | ||||
|         .pair[0] = 4, // TODO make superblock | ||||
|         .pair[1] = 5, | ||||
|     }; | ||||
|  | ||||
|     while (true) { | ||||
|     nextname: | ||||
|         // skip slashes | ||||
|         lookup.name += strspn(lookup.name, "/"); | ||||
|         lookup.size = strcspn(lookup.name, "/"); | ||||
|  | ||||
|         // special case for root dir | ||||
|         if (lookup.name[0] == '\0') { | ||||
|             // TODO set up root? | ||||
| //            *entry = (lfs_entry_t){ | ||||
| //                .d.type = LFS_STRUCT_DIR | LFS_TYPE_DIR, | ||||
| //                .d.u.dir[0] = lfs->root[0], | ||||
| //                .d.u.dir[1] = lfs->root[1], | ||||
| //            }; | ||||
|             return lfs_mktag(LFS_TYPE_DIR_, 0x1ff, 0); | ||||
|         } | ||||
|  | ||||
|         // skip '.' and root '..' | ||||
|         if ((lookup.size == 1 && memcmp(lookup.name, ".", 1) == 0) || | ||||
|             (lookup.size == 2 && memcmp(lookup.name, "..", 2) == 0)) { | ||||
|             lookup.name += lookup.size; | ||||
|             goto nextname; | ||||
|         } | ||||
|  | ||||
|         // skip if matched by '..' in name | ||||
|         const char *suffix = lookup.name + lookup.size; | ||||
|         lfs_size_t suffsize; | ||||
|         int depth = 1; | ||||
|         while (true) { | ||||
|             suffix += strspn(suffix, "/"); | ||||
|             suffsize = strcspn(suffix, "/"); | ||||
|             if (suffsize == 0) { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (suffsize == 2 && memcmp(suffix, "..", 2) == 0) { | ||||
|                 depth -= 1; | ||||
|                 if (depth == 0) { | ||||
|                     lookup.name = suffix + suffsize; | ||||
|                     goto nextname; | ||||
|                 } | ||||
|             } else { | ||||
|                 depth += 1; | ||||
|             } | ||||
|  | ||||
|             suffix += suffsize; | ||||
|         } | ||||
|  | ||||
|         // update what we've found | ||||
|         *path = lookup.name; | ||||
|  | ||||
|         // find path //  TODO handle tails | ||||
|         while (true) { | ||||
|             lookup.tag = 0xffffffff; | ||||
|             lookup.tail[0] = 0xffffffff; | ||||
|             lookup.tail[1] = 0xffffffff; | ||||
|             int err = lfs_dir_fetch_(lfs, dir, lookup.pair, | ||||
|                     lfs_dir_lookuper, &lookup); | ||||
|             if (err) { | ||||
|                 return err; | ||||
|             } | ||||
|  | ||||
|             if (lfs_tag_valid(lookup.tag)) { | ||||
|                 // found it | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             if (lfs_pairisnull(lookup.tail)) { | ||||
|                 return LFS_ERR_NOENT; | ||||
|             } | ||||
|  | ||||
|             lookup.pair[0] = lookup.tail[0]; | ||||
|             lookup.pair[1] = lookup.tail[1]; | ||||
|         } | ||||
|          | ||||
| // TODO handle moves | ||||
| //        // check that entry has not been moved | ||||
| //        if (entry->d.type & LFS_STRUCT_MOVED) { | ||||
| //            int moved = lfs_moved(lfs, &entry->d.u); | ||||
| //            if (moved < 0 || moved) { | ||||
| //                return (moved < 0) ? moved : LFS_ERR_NOENT; | ||||
| //            } | ||||
| // | ||||
| //            entry->d.type &= ~LFS_STRUCT_MOVED; | ||||
| //        } | ||||
|  | ||||
|         lookup.name += lookup.size; | ||||
|         lookup.name += strspn(lookup.name, "/"); | ||||
|         if (lookup.name[0] == '\0') { | ||||
|             return lookup.tag; | ||||
|         } | ||||
|  | ||||
|         // continue on if we hit a directory | ||||
|         // TODO update with what's on master? | ||||
|         if (lfs_tag_type(lookup.tag) != LFS_TYPE_DIR_) { | ||||
|             return LFS_ERR_NOTDIR; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| ////////////////////////////////////////////////////////// | ||||
|  | ||||
| static int lfs_dir_alloc(lfs_t *lfs, lfs_dir_t *dir) { | ||||
|     // allocate pair of dir blocks | ||||
|     for (int i = 0; i < 2; i++) { | ||||
|   | ||||
							
								
								
									
										29
									
								
								lfs.h
									
									
									
									
									
								
							
							
						
						
									
										29
									
								
								lfs.h
									
									
									
									
									
								
							| @@ -105,6 +105,24 @@ enum lfs_type { | ||||
|     LFS_STRUCT_DIR      = 0x20, | ||||
|     LFS_STRUCT_INLINE   = 0x30, | ||||
|     LFS_STRUCT_MOVED    = 0x80, | ||||
|  | ||||
|     LFS_TYPE_CTZ_      = 0x1, | ||||
|     LFS_TYPE_DIR_      = 0x2, | ||||
|     LFS_TYPE_INLINE_   = 0x3, | ||||
|     LFS_TYPE_ATTR_     = 0x4, | ||||
|     LFS_TYPE_NAME_     = 0x5, | ||||
|     LFS_TYPE_DROP_     = 0x7, | ||||
|     LFS_TYPE_TAIL_     = 0xd, | ||||
|     LFS_TYPE_CRC_      = 0xe, | ||||
|  | ||||
| //    LFS_TYPE_DIR_        = 0x002, | ||||
| //    LFS_TYPE_SUPERBLOCK_ = 0xff2, | ||||
|  | ||||
| //    LFS_MASK_ID_         = 0xff000000, | ||||
| //    LFS_MASK_TYPE_       = 0x00fff000, | ||||
| //    LFS_MASK_ATTR_       = 0x00ff0000, | ||||
| //    LFS_MASK_STRUCT_     = 0x0000f000, | ||||
| //    LFS_MASK_SIZE_       = 0x00000fff, | ||||
| }; | ||||
|  | ||||
| // File open flags | ||||
| @@ -314,6 +332,17 @@ typedef struct lfs_dir { | ||||
|     } d; | ||||
| } lfs_dir_t; | ||||
|  | ||||
| typedef struct lfs_dir_ { | ||||
|     lfs_block_t pair[2]; | ||||
|     lfs_block_t tail[2]; | ||||
|  | ||||
|     uint32_t rev; | ||||
|     lfs_off_t off; | ||||
|     uint32_t etag; | ||||
|     uint16_t count; | ||||
|     bool erased; | ||||
| } lfs_dir_t_; | ||||
|  | ||||
| typedef struct lfs_superblock { | ||||
|     struct lfs_disk_superblock { | ||||
|         lfs_block_t root[2]; | ||||
|   | ||||
| @@ -161,6 +161,11 @@ static inline uint32_t lfs_tole32(uint32_t a) { | ||||
|     return lfs_fromle32(a); | ||||
| } | ||||
|  | ||||
| // Align to nearest multiple of a size | ||||
| static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { | ||||
|     return (a + alignment-1) - ((a + alignment-1) % alignment); | ||||
| } | ||||
|  | ||||
| // Calculate CRC-32 with polynomial = 0x04c11db7 | ||||
| void lfs_crc(uint32_t *crc, const void *buffer, size_t size); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user