/**************************************************************************** * fs/littlefs/lfs.c * * This file is a part of NuttX: * * Copyright (C) 2019 Gregory Nutt. All rights reserved. * * Ported by: * * Copyright (C) 2019 Pinecone Inc. All rights reserved. * Author: lihaichen * * This port derives from ARM mbed logic which has a compatible 3-clause * BSD license: * * Copyright (c) 2017, Arm Limited. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the names ARM, NuttX nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /**************************************************************************** * Included Files ****************************************************************************/ #include #include "lfs.h" #include "lfs_util.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ /**************************************************************************** * Private Types ****************************************************************************/ struct lfs_region_s { lfs_off_t oldoff; lfs_size_t oldlen; FAR const void *newdata; lfs_size_t newlen; }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int lfs_pred(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], FAR lfs_dir_t *pdir); static int lfs_parent(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], FAR lfs_dir_t *parent, FAR lfs_entry_t *entry); static int lfs_moved(FAR lfs_t *lfs, FAR const void *e); static int lfs_relocate(FAR lfs_t *lfs, FAR const lfs_block_t oldpair[2], FAR const lfs_block_t newpair[2]); /**************************************************************************** * Private Functions ****************************************************************************/ /* Caching block device operations */ static int lfs_cache_read(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR const lfs_cache_t *pcache, lfs_block_t block, lfs_off_t off, FAR void *buffer, lfs_size_t size) { FAR uint8_t *data = buffer; LFS_ASSERT(block < lfs->cfg->block_count); while (size > 0) { if (pcache && block == pcache->block && off >= pcache->off && off < pcache->off + lfs->cfg->prog_size) { /* is already in pcache? */ lfs_size_t diff = lfs_min(size, lfs->cfg->prog_size - (off - pcache->off)); memcpy(data, &pcache->buffer[off - pcache->off], diff); data += diff; off += diff; size -= diff; continue; } if (block == rcache->block && off >= rcache->off && off < rcache->off + lfs->cfg->read_size) { /* is already in rcache? */ lfs_size_t diff = lfs_min(size, lfs->cfg->read_size - (off - rcache->off)); memcpy(data, &rcache->buffer[off - rcache->off], diff); data += diff; off += diff; size -= diff; continue; } if (off % lfs->cfg->read_size == 0 && size >= lfs->cfg->read_size) { /* bypass cache? */ lfs_size_t diff = size - (size % lfs->cfg->read_size); int err = lfs->cfg->read(lfs->cfg, block, off, data, diff); if (err) { return err; } data += diff; off += diff; size -= diff; continue; } /* load to cache, first condition can no longer fail */ rcache->block = block; rcache->off = off - (off % lfs->cfg->read_size); int err = lfs->cfg->read(lfs->cfg, rcache->block, rcache->off, rcache->buffer, lfs->cfg->read_size); if (err) { return err; } } return 0; } static int lfs_cache_cmp(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR const lfs_cache_t *pcache, lfs_block_t block, lfs_off_t off, FAR const void *buffer, lfs_size_t size) { FAR const uint8_t *data = buffer; lfs_off_t i; for (i = 0; i < size; i++) { uint8_t c; int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1); if (err) { return err; } if (c != data[i]) { return false; } } return true; } static int lfs_cache_crc(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR const lfs_cache_t *pcache, lfs_block_t block, lfs_off_t off, lfs_size_t size, FAR uint32_t *crc) { lfs_off_t i; for (i = 0; i < size; i++) { uint8_t c; int err = lfs_cache_read(lfs, rcache, pcache, block, off + i, &c, 1); if (err) { return err; } lfs_crc(crc, &c, 1); } return 0; } static inline void lfs_cache_drop(FAR lfs_t *lfs, FAR lfs_cache_t *rcache) { /* do not zero, cheaper if cache is readonly or only going to be * written with identical data (during relocates) */ rcache->block = 0xffffffff; } static inline void lfs_cache_zero(FAR lfs_t *lfs, FAR lfs_cache_t *pcache) { /* zero to avoid information leak */ memset(pcache->buffer, 0xff, lfs->cfg->prog_size); pcache->block = 0xffffffff; } static int lfs_cache_flush(FAR lfs_t *lfs, FAR lfs_cache_t *pcache, FAR lfs_cache_t *rcache) { if (pcache->block != 0xffffffff) { int err = lfs->cfg->prog(lfs->cfg, pcache->block, pcache->off, pcache->buffer, lfs->cfg->prog_size); if (err) { return err; } if (rcache) { int res = lfs_cache_cmp(lfs, rcache, NULL, pcache->block, pcache->off, pcache->buffer, lfs->cfg->prog_size); if (res < 0) { return res; } if (!res) { return LFS_ERR_CORRUPT; } } lfs_cache_zero(lfs, pcache); } return 0; } static int lfs_cache_prog(FAR lfs_t *lfs, FAR lfs_cache_t *pcache, FAR lfs_cache_t *rcache, lfs_block_t block, lfs_off_t off, FAR const void *buffer, lfs_size_t size) { FAR const uint8_t *data = buffer; LFS_ASSERT(block < lfs->cfg->block_count); while (size > 0) { if (block == pcache->block && off >= pcache->off && off < pcache->off + lfs->cfg->prog_size) { /* is already in pcache? */ lfs_size_t diff = lfs_min(size, lfs->cfg->prog_size - (off - pcache->off)); memcpy(&pcache->buffer[off - pcache->off], data, diff); data += diff; off += diff; size -= diff; if (off % lfs->cfg->prog_size == 0) { /* eagerly flush out pcache if we fill up */ int err = lfs_cache_flush(lfs, pcache, rcache); if (err) { return err; } } continue; } /* pcache must have been flushed, either by programming and * entire block or manually flushing the pcache */ LFS_ASSERT(pcache->block == 0xffffffff); if (off % lfs->cfg->prog_size == 0 && size >= lfs->cfg->prog_size) { /* bypass pcache? */ lfs_size_t diff = size - (size % lfs->cfg->prog_size); int err = lfs->cfg->prog(lfs->cfg, block, off, data, diff); if (err) { return err; } if (rcache) { int res = lfs_cache_cmp(lfs, rcache, NULL, block, off, data, diff); if (res < 0) { return res; } if (!res) { return LFS_ERR_CORRUPT; } } data += diff; off += diff; size -= diff; continue; } /* prepare pcache, first condition can no longer fail */ pcache->block = block; pcache->off = off - (off % lfs->cfg->prog_size); } return 0; } /* General lfs block device operations */ static int lfs_bd_read(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, FAR 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, block, off, buffer, size); } static int lfs_bd_prog(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, FAR const void *buffer, lfs_size_t size) { return lfs_cache_prog(lfs, &lfs->pcache, NULL, block, off, buffer, size); } static int lfs_bd_cmp(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, FAR const void *buffer, lfs_size_t size) { return lfs_cache_cmp(lfs, &lfs->rcache, NULL, block, off, buffer, size); } static int lfs_bd_crc(FAR lfs_t *lfs, lfs_block_t block, lfs_off_t off, lfs_size_t size, FAR uint32_t *crc) { return lfs_cache_crc(lfs, &lfs->rcache, NULL, block, off, size, crc); } static int lfs_bd_erase(FAR lfs_t *lfs, lfs_block_t block) { return lfs->cfg->erase(lfs->cfg, block); } static int lfs_bd_sync(FAR lfs_t *lfs) { lfs_cache_drop(lfs, &lfs->rcache); int err = lfs_cache_flush(lfs, &lfs->pcache, NULL); if (err) { return err; } return lfs->cfg->sync(lfs->cfg); } /* Block allocator */ static int lfs_alloc_lookahead(FAR void *p, lfs_block_t block) { FAR lfs_t *lfs = p; lfs_block_t off = ((block - lfs->free.off) + lfs->cfg->block_count) % lfs->cfg->block_count; if (off < lfs->free.size) { lfs->free.buffer[off / 32] |= 1U << (off % 32); } return 0; } static int lfs_alloc(FAR lfs_t *lfs, FAR lfs_block_t *block) { while (true) { while (lfs->free.i != lfs->free.size) { lfs_block_t off = lfs->free.i; lfs->free.i += 1; lfs->free.ack -= 1; if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) { /* found a free block */ *block = (lfs->free.off + off) % lfs->cfg->block_count; /* eagerly find next off so an alloc ack can * discredit old lookahead blocks */ while (lfs->free.i != lfs->free.size && (lfs->free.buffer[lfs->free.i / 32] & (1U << (lfs->free.i % 32)))) { lfs->free.i += 1; lfs->free.ack -= 1; } return 0; } } /* check if we have looked at all blocks since last ack */ if (lfs->free.ack == 0) { LFS_WARN("No more free space %" PRIu32, lfs->free.i + lfs->free.off); return LFS_ERR_NOSPC; } lfs->free.off = (lfs->free.off + lfs->free.size) % lfs->cfg->block_count; lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->free.ack); lfs->free.i = 0; /* find mask of free blocks from tree */ memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8); int err = lfs_traverse(lfs, lfs_alloc_lookahead, lfs); if (err) { return err; } } } static void lfs_alloc_ack(FAR lfs_t *lfs) { lfs->free.ack = lfs->cfg->block_count; } /* Endian swapping functions */ static void lfs_dir_fromle32(FAR struct lfs_disk_dir_s *d) { d->rev = lfs_fromle32(d->rev); d->size = lfs_fromle32(d->size); d->tail[0] = lfs_fromle32(d->tail[0]); d->tail[1] = lfs_fromle32(d->tail[1]); } static void lfs_dir_tole32(FAR struct lfs_disk_dir_s *d) { d->rev = lfs_tole32(d->rev); d->size = lfs_tole32(d->size); d->tail[0] = lfs_tole32(d->tail[0]); d->tail[1] = lfs_tole32(d->tail[1]); } static void lfs_entry_fromle32(FAR struct lfs_disk_entry_s *d) { d->u.dir[0] = lfs_fromle32(d->u.dir[0]); d->u.dir[1] = lfs_fromle32(d->u.dir[1]); } static void lfs_entry_tole32(FAR struct lfs_disk_entry_s *d) { d->u.dir[0] = lfs_tole32(d->u.dir[0]); d->u.dir[1] = lfs_tole32(d->u.dir[1]); } static void lfs_superblock_fromle32(FAR struct lfs_disk_superblock_s *d) { d->root[0] = lfs_fromle32(d->root[0]); d->root[1] = lfs_fromle32(d->root[1]); d->block_size = lfs_fromle32(d->block_size); d->block_count = lfs_fromle32(d->block_count); d->version = lfs_fromle32(d->version); } static void lfs_superblock_tole32(FAR struct lfs_disk_superblock_s *d) { d->root[0] = lfs_tole32(d->root[0]); d->root[1] = lfs_tole32(d->root[1]); d->block_size = lfs_tole32(d->block_size); d->block_count = lfs_tole32(d->block_count); d->version = lfs_tole32(d->version); } /* Metadata pair and directory operations */ static inline void lfs_pairswap(FAR lfs_block_t pair[2]) { lfs_block_t t = pair[0]; pair[0] = pair[1]; pair[1] = t; } static inline bool lfs_pairisnull(FAR const lfs_block_t pair[2]) { return pair[0] == 0xffffffff || pair[1] == 0xffffffff; } static inline int lfs_paircmp(FAR const lfs_block_t paira[2], FAR const lfs_block_t pairb[2]) { return !(paira[0] == pairb[0] || paira[1] == pairb[1] || paira[0] == pairb[1] || paira[1] == pairb[0]); } static inline bool lfs_pairsync(FAR const lfs_block_t paira[2], FAR const lfs_block_t pairb[2]) { return (paira[0] == pairb[0] && paira[1] == pairb[1]) || (paira[0] == pairb[1] && paira[1] == pairb[0]); } static inline lfs_size_t lfs_entry_size(FAR const lfs_entry_t *entry) { return 4 + entry->d.elen + entry->d.alen + entry->d.nlen; } static int lfs_dir_alloc(FAR lfs_t *lfs, FAR lfs_dir_t *dir) { int i; /* allocate pair of dir blocks */ for (i = 0; i < 2; i++) { int err = lfs_alloc(lfs, &dir->pair[i]); 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->d.rev, 4); if (err && err != LFS_ERR_CORRUPT) { return err; } if (err != LFS_ERR_CORRUPT) { dir->d.rev = lfs_fromle32(dir->d.rev); } /* set defaults */ dir->d.rev += 1; dir->d.size = sizeof(dir->d) + 4; dir->d.tail[0] = 0xffffffff; dir->d.tail[1] = 0xffffffff; dir->off = sizeof(dir->d); /* don't write out yet, let caller take care of that */ return 0; } static int lfs_dir_fetch(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR const lfs_block_t pair[2]) { /* copy out pair, otherwise may be aliasing dir */ FAR const lfs_block_t tpair[2] = { pair[0], pair[1] }; bool valid = false; int i; /* check both blocks for the most recent revision */ for (i = 0; i < 2; i++) { struct lfs_disk_dir_s test; uint32_t crc; int err = lfs_bd_read(lfs, tpair[i], 0, &test, sizeof(test)); lfs_dir_fromle32(&test); if (err) { if (err == LFS_ERR_CORRUPT) { continue; } return err; } if (valid && lfs_scmp(test.rev, dir->d.rev) < 0) { continue; } if ((0x7fffffff & test.size) < sizeof(test) + 4 || (0x7fffffff & test.size) > lfs->cfg->block_size) { continue; } crc = 0xffffffff; lfs_dir_tole32(&test); lfs_crc(&crc, &test, sizeof(test)); lfs_dir_fromle32(&test); err = lfs_bd_crc(lfs, tpair[i], sizeof(test), (0x7fffffff & test.size) - sizeof(test), &crc); if (err) { if (err == LFS_ERR_CORRUPT) { continue; } return err; } if (crc != 0) { continue; } valid = true; /* setup dir in case it's valid */ dir->pair[0] = tpair[(i + 0) % 2]; dir->pair[1] = tpair[(i + 1) % 2]; dir->off = sizeof(dir->d); dir->d = test; } if (!valid) { LFS_ERROR("Corrupted dir pair at %" PRIu32 " %" PRIu32, tpair[0], tpair[1]); return LFS_ERR_CORRUPT; } return 0; } static int lfs_dir_commit(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR const struct lfs_region_s *regions, int count) { FAR lfs_dir_t *d; lfs_block_t oldpair[2]; bool relocated; int i; /* increment revision count */ dir->d.rev += 1; /* keep pairs in order such that pair[0] is most recent */ lfs_pairswap(dir->pair); for (i = 0; i < count; i++) { dir->d.size += regions[i].newlen - regions[i].oldlen; } oldpair[0] = dir->pair[0]; oldpair[1] = dir->pair[1]; relocated = false; while (true) { if (true) { uint32_t crc; int err = lfs_bd_erase(lfs, dir->pair[0]); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } crc = 0xffffffff; lfs_dir_tole32(&dir->d); lfs_crc(&crc, &dir->d, sizeof(dir->d)); err = lfs_bd_prog(lfs, dir->pair[0], 0, &dir->d, sizeof(dir->d)); lfs_dir_fromle32(&dir->d); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } i = 0; lfs_off_t oldoff = sizeof(dir->d); lfs_off_t newoff = sizeof(dir->d); while (newoff < (0x7fffffff & dir->d.size) - 4) { if (i < count && regions[i].oldoff == oldoff) { lfs_crc(&crc, regions[i].newdata, regions[i].newlen); err = lfs_bd_prog(lfs, dir->pair[0], newoff, regions[i].newdata, regions[i].newlen); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } oldoff += regions[i].oldlen; newoff += regions[i].newlen; i += 1; } else { uint8_t data; err = lfs_bd_read(lfs, oldpair[1], oldoff, &data, 1); if (err) { return err; } lfs_crc(&crc, &data, 1); err = lfs_bd_prog(lfs, dir->pair[0], newoff, &data, 1); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } oldoff += 1; newoff += 1; } } crc = lfs_tole32(crc); err = lfs_bd_prog(lfs, dir->pair[0], newoff, &crc, 4); crc = lfs_fromle32(crc); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } err = lfs_bd_sync(lfs); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } /* successful commit, check checksum to make sure */ uint32_t ncrc = 0xffffffff; err = lfs_bd_crc(lfs, dir->pair[0], 0, (0x7fffffff & dir->d.size) - 4, &ncrc); if (err) { return err; } if (ncrc != crc) { goto relocate; } } break; relocate: /* Commit was corrupted */ LFS_DEBUG("Bad block at %" PRIu32, dir->pair[0]); /* Drop caches and prepare to relocate block */ relocated = true; lfs_cache_drop(lfs, &lfs->pcache); /* Can't relocate superblock, filesystem is now frozen */ if (lfs_paircmp(oldpair, (const lfs_block_t[2]){ 0, 1 }) == 0) { LFS_WARN("Superblock %" PRIu32 " has become unwritable", oldpair[0]); return LFS_ERR_CORRUPT; } /* relocate half of pair */ int err = lfs_alloc(lfs, &dir->pair[0]); if (err) { return err; } } if (relocated) { /* update references if we relocated */ LFS_DEBUG("Relocating %" PRIu32 " %" PRIu32 " to %" PRIu32 " %" PRIu32, 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 (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_update(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR lfs_entry_t *entry, FAR const void *data) { int err; lfs_entry_tole32(&entry->d); err = lfs_dir_commit( lfs, dir, (struct lfs_region_s[]) { { entry->off, sizeof(entry->d), &entry->d, sizeof(entry->d) }, { entry->off + sizeof(entry->d), entry->d.nlen, data, entry->d.nlen } }, data ? 2 : 1); lfs_entry_fromle32(&entry->d); return err; } static int lfs_dir_append(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR lfs_entry_t *entry, FAR const void *data) { /* check if we fit, if top bit is set we do not and move on */ while (true) { if (dir->d.size + lfs_entry_size(entry) <= lfs->cfg->block_size) { entry->off = dir->d.size - 4; lfs_entry_tole32(&entry->d); int err = lfs_dir_commit(lfs, dir, (struct lfs_region_s[]) { { entry->off, 0, &entry->d, sizeof(entry->d) }, { entry->off, 0, data, entry->d.nlen } }, 2); lfs_entry_fromle32(&entry->d); return err; } /* we need to allocate a new dir block */ if (!(0x80000000 & dir->d.size)) { lfs_dir_t olddir = *dir; int err = lfs_dir_alloc(lfs, dir); if (err) { return err; } dir->d.tail[0] = olddir.d.tail[0]; dir->d.tail[1] = olddir.d.tail[1]; entry->off = dir->d.size - 4; lfs_entry_tole32(&entry->d); err = lfs_dir_commit(lfs, dir, (struct lfs_region_s[]) { { entry->off, 0, &entry->d, sizeof(entry->d) }, { entry->off, 0, data, entry->d.nlen } }, 2); lfs_entry_fromle32(&entry->d); if (err) { return err; } olddir.d.size |= 0x80000000; olddir.d.tail[0] = dir->pair[0]; olddir.d.tail[1] = dir->pair[1]; return lfs_dir_commit(lfs, &olddir, NULL, 0); } int err = lfs_dir_fetch(lfs, dir, dir->d.tail); if (err) { return err; } } } static int lfs_dir_remove(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR lfs_entry_t *entry) { FAR lfs_file_t *f; FAR lfs_dir_t *d; int err; /* check if we should just drop the directory block */ if ((dir->d.size & 0x7fffffff) == sizeof(dir->d) + 4 + lfs_entry_size(entry)) { lfs_dir_t pdir; int res = lfs_pred(lfs, dir->pair, &pdir); if (res < 0) { return res; } if (pdir.d.size & 0x80000000) { pdir.d.size &= dir->d.size | 0x7fffffff; pdir.d.tail[0] = dir->d.tail[0]; pdir.d.tail[1] = dir->d.tail[1]; return lfs_dir_commit(lfs, &pdir, NULL, 0); } } /* shift out the entry */ err = lfs_dir_commit(lfs, dir, (struct lfs_region_s[]) { { entry->off, lfs_entry_size(entry), NULL, 0 }, }, 1); if (err) { return err; } /* shift over any files/directories that are affected */ for (f = lfs->files; f; f = f->next) { if (lfs_paircmp(f->pair, dir->pair) == 0) { if (f->poff == entry->off) { f->pair[0] = 0xffffffff; f->pair[1] = 0xffffffff; } else if (f->poff > entry->off) { f->poff -= lfs_entry_size(entry); } } } for (d = lfs->dirs; d; d = d->next) { if (lfs_paircmp(d->pair, dir->pair) == 0) { if (d->off > entry->off) { d->off -= lfs_entry_size(entry); d->pos -= lfs_entry_size(entry); } } } return 0; } static int lfs_dir_next(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR lfs_entry_t *entry) { while (dir->off + sizeof(entry->d) > (0x7fffffff & dir->d.size) - 4) { if (!(0x80000000 & dir->d.size)) { entry->off = dir->off; return LFS_ERR_NOENT; } int err = lfs_dir_fetch(lfs, dir, dir->d.tail); if (err) { return err; } dir->off = sizeof(dir->d); dir->pos += sizeof(dir->d) + 4; } int err = lfs_bd_read(lfs, dir->pair[0], dir->off, &entry->d, sizeof(entry->d)); lfs_entry_fromle32(&entry->d); if (err) { return err; } entry->off = dir->off; dir->off += lfs_entry_size(entry); dir->pos += lfs_entry_size(entry); return 0; } static int lfs_dir_find(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR lfs_entry_t *entry, FAR const char **path) { FAR const char *pathname = *path; size_t pathlen; entry->d.type = LFS_TYPE_DIR; entry->d.elen = sizeof(entry->d) - 4; entry->d.alen = 0; entry->d.nlen = 0; entry->d.u.dir[0] = lfs->root[0]; entry->d.u.dir[1] = lfs->root[1]; while (true) { FAR const char *suffix; size_t sufflen; int depth; nextname: /* skip slashes */ pathname += strspn(pathname, "/"); pathlen = strcspn(pathname, "/"); /* skip '.' and root '..' */ if ((pathlen == 1 && memcmp(pathname, ".", 1) == 0) || (pathlen == 2 && memcmp(pathname, "..", 2) == 0)) { pathname += pathlen; goto nextname; } /* skip if matched by '..' in name */ suffix = pathname + pathlen; depth = 1; while (true) { suffix += strspn(suffix, "/"); sufflen = strcspn(suffix, "/"); if (sufflen == 0) { break; } if (sufflen == 2 && memcmp(suffix, "..", 2) == 0) { depth -= 1; if (depth == 0) { pathname = suffix + sufflen; goto nextname; } } else { depth += 1; } suffix += sufflen; } /* found path */ if (pathname[0] == '\0') { return 0; } /* update what we've found */ *path = pathname; /* continue on if we hit a directory */ if (entry->d.type != LFS_TYPE_DIR) { return LFS_ERR_NOTDIR; } int err = lfs_dir_fetch(lfs, dir, entry->d.u.dir); if (err) { return err; } /* find entry matching name */ while (true) { err = lfs_dir_next(lfs, dir, entry); if (err) { return err; } if (((0x7f & entry->d.type) != LFS_TYPE_REG && (0x7f & entry->d.type) != LFS_TYPE_DIR) || entry->d.nlen != pathlen) { continue; } int res = lfs_bd_cmp(lfs, dir->pair[0], entry->off + 4 + entry->d.elen + entry->d.alen, pathname, pathlen); if (res < 0) { return res; } /* found match */ if (res) { break; } } /* check that entry has not been moved */ if (!lfs->moving && entry->d.type & 0x80) { int moved = lfs_moved(lfs, &entry->d.u); if (moved < 0 || moved) { return (moved < 0) ? moved : LFS_ERR_NOENT; } entry->d.type &= ~0x80; } /* to next name */ pathname += pathlen; } } /* File index list operations */ static int lfs_ctz_index(FAR lfs_t *lfs, FAR lfs_off_t *off) { lfs_off_t size = *off; lfs_off_t b = lfs->cfg->block_size - 2 * 4; lfs_off_t i = size / b; if (i == 0) { return 0; } i = (size - 4 * (lfs_popc(i - 1) + 2)) / b; *off = size - b * i - 4 * lfs_popc(i); return i; } static int lfs_ctz_find(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR const lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, lfs_size_t pos, FAR lfs_block_t *block, FAR lfs_off_t *off) { if (size == 0) { *block = 0xffffffff; *off = 0; return 0; } lfs_off_t current = lfs_ctz_index(lfs, &(lfs_off_t){ size - 1 }); lfs_off_t target = lfs_ctz_index(lfs, &pos); while (current > target) { int err; lfs_size_t skip = lfs_min(lfs_npw2(current - target + 1) - 1, lfs_ctz(current)); err = lfs_cache_read(lfs, rcache, pcache, head, 4 * skip, &head, 4); head = lfs_fromle32(head); if (err) { return err; } LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); current -= 1 << skip; } *block = head; *off = pos; return 0; } static int lfs_ctz_extend(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR lfs_cache_t *pcache, lfs_block_t head, lfs_size_t size, FAR lfs_block_t *block, FAR lfs_off_t *off) { while (true) { lfs_block_t nblock; int err; /* go ahead and grab a block */ err = lfs_alloc(lfs, &nblock); if (err) { return err; } LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count); if (true) { lfs_off_t index; lfs_size_t skips; lfs_off_t i; err = lfs_bd_erase(lfs, nblock); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } if (size == 0) { *block = nblock; *off = 0; return 0; } size -= 1; index = lfs_ctz_index(lfs, &size); size += 1; /* just copy out the last block if it is incomplete */ if (size != lfs->cfg->block_size) { for (i = 0; i < size; i++) { uint8_t data; err = lfs_cache_read(lfs, rcache, NULL, head, i, &data, 1); if (err) { return err; } err = lfs_cache_prog(lfs, pcache, rcache, nblock, i, &data, 1); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } } *block = nblock; *off = size; return 0; } /* append block */ index += 1; skips = lfs_ctz(index) + 1; for (i = 0; i < skips; i++) { head = lfs_tole32(head); err = lfs_cache_prog(lfs, pcache, rcache, nblock, 4 * i, &head, 4); head = lfs_fromle32(head); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } if (i != skips - 1) { err = lfs_cache_read(lfs, rcache, NULL, head, 4 * i, &head, 4); head = lfs_fromle32(head); if (err) { return err; } } LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count); } *block = nblock; *off = 4 * skips; return 0; } relocate: LFS_DEBUG("Bad block at %" PRIu32, nblock); /* just clear cache and try a new block */ lfs_cache_drop(lfs, &lfs->pcache); } } static int lfs_ctz_traverse(FAR lfs_t *lfs, FAR lfs_cache_t *rcache, FAR const lfs_cache_t *pcache, lfs_block_t head, FAR lfs_size_t size, CODE int (*cb)(FAR void *, lfs_block_t), FAR void *data) { lfs_off_t index; if (size == 0) { return 0; } index = lfs_ctz_index(lfs, &(lfs_off_t){ size - 1 }); while (true) { lfs_block_t heads[2]; int count; int err; int i; err = cb(data, head); if (err) { return err; } if (index == 0) { return 0; } count = 2 - (index & 1); err = lfs_cache_read(lfs, rcache, pcache, head, 0, &heads, count * 4); heads[0] = lfs_fromle32(heads[0]); heads[1] = lfs_fromle32(heads[1]); if (err) { return err; } for (i = 0; i < count - 1; i++) { err = cb(data, heads[i]); if (err) { return err; } } head = heads[count - 1]; index -= count; } } static int lfs_file_relocate(FAR lfs_t *lfs, FAR lfs_file_t *file) { lfs_block_t nblock; lfs_off_t i; int err; relocate: LFS_DEBUG("Bad block at %" PRIu32, file->block); /* just relocate what exists into new block */ err = lfs_alloc(lfs, &nblock); if (err) { return err; } err = lfs_bd_erase(lfs, nblock); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } /* either read from dirty cache or disk */ for (i = 0; i < file->off; i++) { uint8_t data; err = lfs_cache_read(lfs, &lfs->rcache, &file->cache, file->block, i, &data, 1); if (err) { return err; } err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, nblock, i, &data, 1); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } } /* copy over new state of file */ memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->prog_size); file->cache.block = lfs->pcache.block; file->cache.off = lfs->pcache.off; lfs_cache_zero(lfs, &lfs->pcache); file->block = nblock; return 0; } static int lfs_file_flush(FAR lfs_t *lfs, FAR lfs_file_t *file) { if (file->flags & LFS_F_READING) { /* just drop read cache */ lfs_cache_drop(lfs, &file->cache); file->flags &= ~LFS_F_READING; } if (file->flags & LFS_F_WRITING) { lfs_off_t pos = file->pos; /* copy over anything after current branch */ lfs_file_t orig = { .head = file->head, .size = file->size, .flags = LFS_O_RDONLY, .pos = file->pos, .cache = lfs->rcache, }; lfs_cache_drop(lfs, &lfs->rcache); while (file->pos < file->size) { /* copy over a byte at a time, leave it up to caching * to make this efficient */ uint8_t data; lfs_ssize_t res = lfs_file_read(lfs, &orig, &data, 1); if (res < 0) { return res; } res = lfs_file_write(lfs, file, &data, 1); if (res < 0) { return res; } /* keep our reference to the rcache in sync */ if (lfs->rcache.block != 0xffffffff) { lfs_cache_drop(lfs, &orig.cache); lfs_cache_drop(lfs, &lfs->rcache); } } /* write out what we have */ while (true) { int err = lfs_cache_flush(lfs, &file->cache, &lfs->rcache); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } return err; } break; relocate: err = lfs_file_relocate(lfs, file); if (err) { return err; } } /* actual file updates */ file->head = file->block; file->size = file->pos; file->flags &= ~LFS_F_WRITING; file->flags |= LFS_F_DIRTY; file->pos = pos; } return 0; } /* Filesystem operations */ static void lfs_deinit(FAR lfs_t *lfs) { /* free allocated memory */ if (!lfs->cfg->read_buffer) { lfs_free(lfs->rcache.buffer); } if (!lfs->cfg->prog_buffer) { lfs_free(lfs->pcache.buffer); } if (!lfs->cfg->lookahead_buffer) { lfs_free(lfs->free.buffer); } } static int lfs_init(FAR lfs_t *lfs, FAR const struct lfs_config_s *cfg) { lfs->cfg = cfg; /* setup read cache */ if (lfs->cfg->read_buffer) { lfs->rcache.buffer = lfs->cfg->read_buffer; } else { lfs->rcache.buffer = lfs_malloc(lfs->cfg->read_size); if (!lfs->rcache.buffer) { goto cleanup; } } /* setup program cache */ if (lfs->cfg->prog_buffer) { lfs->pcache.buffer = lfs->cfg->prog_buffer; } else { lfs->pcache.buffer = lfs_malloc(lfs->cfg->prog_size); if (!lfs->pcache.buffer) { goto cleanup; } } /* zero to avoid information leaks */ lfs_cache_zero(lfs, &lfs->pcache); lfs_cache_drop(lfs, &lfs->rcache); /* setup lookahead, round down to nearest 32-bits */ LFS_ASSERT(lfs->cfg->lookahead % 32 == 0); LFS_ASSERT(lfs->cfg->lookahead > 0); if (lfs->cfg->lookahead_buffer) { lfs->free.buffer = lfs->cfg->lookahead_buffer; } else { lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead / 8); if (!lfs->free.buffer) { goto cleanup; } } /* check that program and read sizes are multiples of the block size */ LFS_ASSERT(lfs->cfg->prog_size % lfs->cfg->read_size == 0); LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->prog_size == 0); /* check that the block size is large enough to fit ctz pointers */ LFS_ASSERT(4 * lfs_npw2(0xffffffff / (lfs->cfg->block_size - 2 * 4)) <= lfs->cfg->block_size); /* setup default state */ lfs->root[0] = 0xffffffff; lfs->root[1] = 0xffffffff; lfs->files = NULL; lfs->dirs = NULL; lfs->deorphaned = false; lfs->moving = false; return 0; cleanup: lfs_deinit(lfs); return LFS_ERR_NOMEM; } static int lfs_pred(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], FAR lfs_dir_t *pdir) { int err; if (lfs_pairisnull(lfs->root)) { return 0; } /* iterate over all directory directory entries */ err = lfs_dir_fetch(lfs, pdir, (FAR const lfs_block_t[2]){ 0, 1 }); if (err) { return err; } while (!lfs_pairisnull(pdir->d.tail)) { if (lfs_paircmp(pdir->d.tail, dir) == 0) { return true; } err = lfs_dir_fetch(lfs, pdir, pdir->d.tail); if (err) { return err; } } return false; } static int lfs_parent(FAR lfs_t *lfs, FAR const lfs_block_t dir[2], FAR lfs_dir_t *parent, FAR lfs_entry_t *entry) { if (lfs_pairisnull(lfs->root)) { return 0; } parent->d.tail[0] = 0; parent->d.tail[1] = 1; /* iterate over all directory directory entries */ while (!lfs_pairisnull(parent->d.tail)) { int err = lfs_dir_fetch(lfs, parent, parent->d.tail); if (err) { return err; } while (true) { err = lfs_dir_next(lfs, parent, entry); if (err && err != LFS_ERR_NOENT) { return err; } if (err == LFS_ERR_NOENT) { break; } if (((0x70 & entry->d.type) == (0x70 & LFS_TYPE_DIR)) && lfs_paircmp(entry->d.u.dir, dir) == 0) { return true; } } } return false; } static int lfs_moved(FAR lfs_t *lfs, FAR const void *e) { lfs_dir_t cwd; lfs_entry_t entry; int err; if (lfs_pairisnull(lfs->root)) { return 0; } /* skip superblock */ err = lfs_dir_fetch(lfs, &cwd, (const lfs_block_t[2]){ 0, 1 }); if (err) { return err; } /* iterate over all directory directory entries */ while (!lfs_pairisnull(cwd.d.tail)) { err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); if (err) { return err; } while (true) { err = lfs_dir_next(lfs, &cwd, &entry); if (err && err != LFS_ERR_NOENT) { return err; } if (err == LFS_ERR_NOENT) { break; } if (!(0x80 & entry.d.type) && memcmp(&entry.d.u, e, sizeof(entry.d.u)) == 0) { return true; } } } return false; } static int lfs_relocate(FAR lfs_t *lfs, FAR const lfs_block_t oldpair[2], FAR const lfs_block_t newpair[2]) { lfs_dir_t parent; lfs_entry_t entry; int res; /* find parent */ res = lfs_parent(lfs, oldpair, &parent, &entry); if (res < 0) { return res; } if (res) { int err; /* update disk, this creates a desync */ entry.d.u.dir[0] = newpair[0]; entry.d.u.dir[1] = newpair[1]; err = lfs_dir_update(lfs, &parent, &entry, NULL); if (err) { return err; } /* update internal root */ if (lfs_paircmp(oldpair, lfs->root) == 0) { LFS_DEBUG("Relocating root %" PRIu32 " %" PRIu32, newpair[0], newpair[1]); lfs->root[0] = newpair[0]; lfs->root[1] = newpair[1]; } /* clean up bad block, which should now be a desync */ return lfs_deorphan(lfs); } /* find pred */ res = lfs_pred(lfs, oldpair, &parent); if (res < 0) { return res; } if (res) { /* just replace bad pair, no desync can occur */ parent.d.tail[0] = newpair[0]; parent.d.tail[1] = newpair[1]; return lfs_dir_commit(lfs, &parent, NULL, 0); } /* couldn't find dir, must be new */ return 0; } /**************************************************************************** * Public Functions ****************************************************************************/ /* Top level directory operations */ int lfs_mkdir(FAR lfs_t *lfs, FAR const char *path) { lfs_dir_t cwd; lfs_dir_t dir; lfs_entry_t entry; int err; /* deorphan if we haven't yet, needed at most once after poweron */ if (!lfs->deorphaned) { err = lfs_deorphan(lfs); if (err) { return err; } } /* fetch parent directory */ err = lfs_dir_find(lfs, &cwd, &entry, &path); if (err != LFS_ERR_NOENT || strchr(path, '/') != NULL) { return err ? err : LFS_ERR_EXIST; } /* build up new directory */ lfs_alloc_ack(lfs); err = lfs_dir_alloc(lfs, &dir); if (err) { return err; } dir.d.tail[0] = cwd.d.tail[0]; dir.d.tail[1] = cwd.d.tail[1]; err = lfs_dir_commit(lfs, &dir, NULL, 0); if (err) { return err; } entry.d.type = LFS_TYPE_DIR; entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; entry.d.nlen = strlen(path); entry.d.u.dir[0] = dir.pair[0]; entry.d.u.dir[1] = dir.pair[1]; cwd.d.tail[0] = dir.pair[0]; cwd.d.tail[1] = dir.pair[1]; err = lfs_dir_append(lfs, &cwd, &entry, path); if (err) { return err; } lfs_alloc_ack(lfs); return 0; } int lfs_dir_open(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR const char *path) { lfs_entry_t entry; int err; dir->pair[0] = lfs->root[0]; dir->pair[1] = lfs->root[1]; err = lfs_dir_find(lfs, dir, &entry, &path); if (err) { return err; } else if (entry.d.type != LFS_TYPE_DIR) { return LFS_ERR_NOTDIR; } err = lfs_dir_fetch(lfs, dir, entry.d.u.dir); if (err) { return err; } /* setup head dir * special offset for '.' and '..' */ dir->head[0] = dir->pair[0]; dir->head[1] = dir->pair[1]; dir->pos = sizeof(dir->d) - 2; dir->off = sizeof(dir->d); /* add to list of directories */ dir->next = lfs->dirs; lfs->dirs = dir; return 0; } int lfs_dir_close(FAR lfs_t *lfs, FAR lfs_dir_t *dir) { FAR lfs_dir_t **p; /* remove from list of directories */ for (p = &lfs->dirs; *p; p = &(*p)->next) { if (*p == dir) { *p = dir->next; break; } } return 0; } int lfs_dir_read(FAR lfs_t *lfs, FAR lfs_dir_t *dir, FAR struct lfs_info_s *info) { lfs_entry_t entry; memset(info, 0, sizeof(*info)); /* special offset for '.' and '..' */ if (dir->pos == sizeof(dir->d) - 2) { info->type = LFS_TYPE_DIR; strcpy(info->name, "."); dir->pos += 1; return 1; } else if (dir->pos == sizeof(dir->d) - 1) { info->type = LFS_TYPE_DIR; strcpy(info->name, ".."); dir->pos += 1; return 1; } while (true) { int err = lfs_dir_next(lfs, dir, &entry); if (err) { return (err == LFS_ERR_NOENT) ? 0 : err; } if ((0x7f & entry.d.type) != LFS_TYPE_REG && (0x7f & entry.d.type) != LFS_TYPE_DIR) { continue; } /* check that entry has not been moved */ if (entry.d.type & 0x80) { int moved = lfs_moved(lfs, &entry.d.u); if (moved < 0) { return moved; } if (moved) { continue; } entry.d.type &= ~0x80; } break; } info->type = entry.d.type; if (info->type == LFS_TYPE_REG) { info->size = entry.d.u.file.size; } int err = lfs_bd_read(lfs, dir->pair[0], entry.off + 4 + entry.d.elen + entry.d.alen, info->name, entry.d.nlen); if (err) { return err; } return 1; } int lfs_dir_seek(FAR lfs_t *lfs, FAR lfs_dir_t *dir, lfs_off_t off) { int err; /* simply walk from head dir */ err = lfs_dir_rewind(lfs, dir); if (err) { return err; } dir->pos = off; while (off > (0x7fffffff & dir->d.size)) { off -= 0x7fffffff & dir->d.size; if (!(0x80000000 & dir->d.size)) { return LFS_ERR_INVAL; } err = lfs_dir_fetch(lfs, dir, dir->d.tail); if (err) { return err; } } dir->off = off; return 0; } lfs_soff_t lfs_dir_tell(FAR lfs_t *lfs, FAR lfs_dir_t *dir) { return dir->pos; } int lfs_dir_rewind(FAR lfs_t *lfs, FAR lfs_dir_t *dir) { int err; /* reload the head dir */ err = lfs_dir_fetch(lfs, dir, dir->head); if (err) { return err; } dir->pair[0] = dir->head[0]; dir->pair[1] = dir->head[1]; dir->pos = sizeof(dir->d) - 2; dir->off = sizeof(dir->d); return 0; } /* Top level file operations */ int lfs_file_opencfg(FAR lfs_t *lfs, FAR lfs_file_t *file, FAR const char *path, int flags, FAR const struct lfs_file_config_s *cfg) { lfs_dir_t cwd; lfs_entry_t entry; int err; /* deorphan if we haven't yet, needed at most once after poweron */ if ((flags & 3) != LFS_O_RDONLY && !lfs->deorphaned) { err = lfs_deorphan(lfs); if (err) { return err; } } /* allocate entry for file if it doesn't exist */ err = lfs_dir_find(lfs, &cwd, &entry, &path); if (err && (err != LFS_ERR_NOENT || strchr(path, '/') != NULL)) { return err; } if (err == LFS_ERR_NOENT) { if (!(flags & LFS_O_CREAT)) { return LFS_ERR_NOENT; } /* create entry to remember name */ entry.d.type = LFS_TYPE_REG; entry.d.elen = sizeof(entry.d) - 4; entry.d.alen = 0; entry.d.nlen = strlen(path); entry.d.u.file.head = 0xffffffff; entry.d.u.file.size = 0; err = lfs_dir_append(lfs, &cwd, &entry, path); if (err) { return err; } } else if (entry.d.type == LFS_TYPE_DIR) { return LFS_ERR_ISDIR; } else if (flags & LFS_O_EXCL) { return LFS_ERR_EXIST; } /* setup file struct */ file->cfg = cfg; file->pair[0] = cwd.pair[0]; file->pair[1] = cwd.pair[1]; file->poff = entry.off; file->head = entry.d.u.file.head; file->size = entry.d.u.file.size; file->flags = flags; file->pos = 0; if (flags & LFS_O_TRUNC) { if (file->size != 0) { file->flags |= LFS_F_DIRTY; } file->head = 0xffffffff; file->size = 0; } /* allocate buffer if needed */ file->cache.block = 0xffffffff; if (file->cfg && file->cfg->buffer) { file->cache.buffer = file->cfg->buffer; } else if (lfs->cfg->file_buffer) { if (lfs->files) { /* already in use */ return LFS_ERR_NOMEM; } file->cache.buffer = lfs->cfg->file_buffer; } else if ((file->flags & 3) == LFS_O_RDONLY) { file->cache.buffer = lfs_malloc(lfs->cfg->read_size); if (!file->cache.buffer) { return LFS_ERR_NOMEM; } } else { file->cache.buffer = lfs_malloc(lfs->cfg->prog_size); if (!file->cache.buffer) { return LFS_ERR_NOMEM; } } /* zero to avoid information leak */ lfs_cache_drop(lfs, &file->cache); if ((file->flags & 3) != LFS_O_RDONLY) { lfs_cache_zero(lfs, &file->cache); } /* add to list of files */ file->next = lfs->files; lfs->files = file; return 0; } int lfs_file_open(FAR lfs_t *lfs, FAR lfs_file_t *file, FAR const char *path, int flags) { return lfs_file_opencfg(lfs, file, path, flags, NULL); } int lfs_file_close(FAR lfs_t *lfs, FAR lfs_file_t *file) { FAR lfs_file_t **p; int err; err = lfs_file_sync(lfs, file); /* remove from list of files */ for (p = &lfs->files; *p; p = &(*p)->next) { if (*p == file) { *p = file->next; break; } } /* clean up memory */ if (!(file->cfg && file->cfg->buffer) && !lfs->cfg->file_buffer) { lfs_free(file->cache.buffer); } return err; } int lfs_file_sync(FAR lfs_t *lfs, FAR lfs_file_t *file) { int err = lfs_file_flush(lfs, file); if (err) { return err; } if ((file->flags & LFS_F_DIRTY) && !(file->flags & LFS_F_ERRED) && !lfs_pairisnull(file->pair)) { lfs_dir_t cwd; lfs_entry_t entry = { .off = file->poff }; /* update dir entry */ err = lfs_dir_fetch(lfs, &cwd, file->pair); if (err) { return err; } err = lfs_bd_read(lfs, cwd.pair[0], entry.off, &entry.d, sizeof(entry.d)); lfs_entry_fromle32(&entry.d); if (err) { return err; } LFS_ASSERT(entry.d.type == LFS_TYPE_REG); entry.d.u.file.head = file->head; entry.d.u.file.size = file->size; err = lfs_dir_update(lfs, &cwd, &entry, NULL); if (err) { return err; } file->flags &= ~LFS_F_DIRTY; } return 0; } lfs_ssize_t lfs_file_read(FAR lfs_t *lfs, FAR lfs_file_t *file, FAR void *buffer, lfs_size_t size) { FAR uint8_t *data = buffer; lfs_size_t nsize = size; if ((file->flags & 3) == LFS_O_WRONLY) { return LFS_ERR_BADF; } if (file->flags & LFS_F_WRITING) { /* flush out any writes */ int err = lfs_file_flush(lfs, file); if (err) { return err; } } if (file->pos >= file->size) { /* eof if past end */ return 0; } size = lfs_min(size, file->size - file->pos); nsize = size; while (nsize > 0) { /* check if we need a new block */ if (!(file->flags & LFS_F_READING) || file->off == lfs->cfg->block_size) { int err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, file->pos, &file->block, &file->off); if (err) { return err; } file->flags |= LFS_F_READING; } /* read as much as we can in current block */ lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off); int err = lfs_cache_read(lfs, &file->cache, NULL, file->block, file->off, data, diff); if (err) { return err; } file->pos += diff; file->off += diff; data += diff; nsize -= diff; } return size; } lfs_ssize_t lfs_file_write(FAR lfs_t *lfs, FAR lfs_file_t *file, FAR const void *buffer, lfs_size_t size) { FAR const uint8_t *data = buffer; lfs_size_t nsize = size; if ((file->flags & 3) == LFS_O_RDONLY) { return LFS_ERR_BADF; } if (file->flags & LFS_F_READING) { /* drop any reads */ int err = lfs_file_flush(lfs, file); if (err) { return err; } } if ((file->flags & LFS_O_APPEND) && file->pos < file->size) { file->pos = file->size; } if (file->pos + size > LFS_FILE_MAX) { /* larger than file limit? */ return LFS_ERR_FBIG; } if (!(file->flags & LFS_F_WRITING) && file->pos > file->size) { /* fill with zeros */ lfs_off_t pos = file->pos; file->pos = file->size; while (file->pos < pos) { lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){ 0 }, 1); if (res < 0) { return res; } } } while (nsize > 0) { lfs_size_t diff; /* check if we need a new block */ if (!(file->flags & LFS_F_WRITING) || file->off == lfs->cfg->block_size) { int err; if (!(file->flags & LFS_F_WRITING) && file->pos > 0) { /* find out which block we're extending from */ err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, file->pos - 1, &file->block, &file->off); if (err) { file->flags |= LFS_F_ERRED; return err; } /* mark cache as dirty since we may have read data into it */ lfs_cache_zero(lfs, &file->cache); } /* extend file with new blocks */ lfs_alloc_ack(lfs); err = lfs_ctz_extend(lfs, &lfs->rcache, &file->cache, file->block, file->pos, &file->block, &file->off); if (err) { file->flags |= LFS_F_ERRED; return err; } file->flags |= LFS_F_WRITING; } /* program as much as we can in current block */ diff = lfs_min(nsize, lfs->cfg->block_size - file->off); while (true) { int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, file->block, file->off, data, diff); if (err) { if (err == LFS_ERR_CORRUPT) { goto relocate; } file->flags |= LFS_F_ERRED; return err; } break; relocate: err = lfs_file_relocate(lfs, file); if (err) { file->flags |= LFS_F_ERRED; return err; } } file->pos += diff; file->off += diff; data += diff; nsize -= diff; lfs_alloc_ack(lfs); } file->flags &= ~LFS_F_ERRED; return size; } lfs_soff_t lfs_file_seek(FAR lfs_t *lfs, FAR lfs_file_t *file, lfs_soff_t off, int whence) { lfs_soff_t npos; int err; /* write out everything beforehand, may be noop if rdonly */ err = lfs_file_flush(lfs, file); if (err) { return err; } /* find new pos */ npos = file->pos; if (whence == LFS_SEEK_SET) { npos = off; } else if (whence == LFS_SEEK_CUR) { npos = file->pos + off; } else if (whence == LFS_SEEK_END) { npos = file->size + off; } if (npos < 0 || npos > LFS_FILE_MAX) { /* file position out of range */ return LFS_ERR_INVAL; } /* update pos */ file->pos = npos; return npos; } int lfs_file_truncate(FAR lfs_t *lfs, FAR lfs_file_t *file, lfs_off_t size) { lfs_off_t oldsize; if ((file->flags & 3) == LFS_O_RDONLY) { return LFS_ERR_BADF; } oldsize = lfs_file_size(lfs, file); if (size < oldsize) { /* need to flush since directly changing metadata */ int err = lfs_file_flush(lfs, file); if (err) { return err; } /* lookup new head in ctz skip list */ err = lfs_ctz_find(lfs, &file->cache, NULL, file->head, file->size, size, &file->head, &(lfs_off_t){ 0 }); if (err) { return err; } file->size = size; file->flags |= LFS_F_DIRTY; } else if (size > oldsize) { int err; lfs_off_t pos = file->pos; /* flush+seek if not already at end */ if (file->pos != oldsize) { err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END); if (err < 0) { return err; } } /* fill with zeros */ while (file->pos < size) { lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){ 0 }, 1); if (res < 0) { return res; } } /* restore pos */ err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET); if (err < 0) { return err; } } return 0; } lfs_soff_t lfs_file_tell(FAR lfs_t *lfs, FAR lfs_file_t *file) { return file->pos; } int lfs_file_rewind(FAR lfs_t *lfs, FAR lfs_file_t *file) { lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET); if (res < 0) { return res; } return 0; } lfs_soff_t lfs_file_size(FAR lfs_t *lfs, FAR lfs_file_t *file) { if (file->flags & LFS_F_WRITING) { return lfs_max(file->pos, file->size); } else { return file->size; } } /* General fs operations */ int lfs_stat(FAR lfs_t *lfs, FAR const char *path, FAR struct lfs_info_s *info) { lfs_dir_t cwd; lfs_entry_t entry; int err = lfs_dir_find(lfs, &cwd, &entry, &path); if (err) { return err; } memset(info, 0, sizeof(*info)); info->type = entry.d.type; if (info->type == LFS_TYPE_REG) { info->size = entry.d.u.file.size; } if (lfs_paircmp(entry.d.u.dir, lfs->root) == 0) { strcpy(info->name, "/"); } else { err = lfs_bd_read(lfs, cwd.pair[0], entry.off + 4 + entry.d.elen + entry.d.alen, info->name, entry.d.nlen); if (err) { return err; } } return 0; } int lfs_remove(FAR lfs_t *lfs, FAR const char *path) { lfs_dir_t cwd; lfs_entry_t entry; int err; /* deorphan if we haven't yet, needed at most once after poweron */ if (!lfs->deorphaned) { err = lfs_deorphan(lfs); if (err) { return err; } } err = lfs_dir_find(lfs, &cwd, &entry, &path); if (err) { return err; } lfs_dir_t dir; if (entry.d.type == LFS_TYPE_DIR) { /* must be empty before removal, checking size * without masking top bit checks for any case where * dir is not empty */ err = lfs_dir_fetch(lfs, &dir, entry.d.u.dir); if (err) { return err; } else if (dir.d.size != sizeof(dir.d) + 4) { return LFS_ERR_NOTEMPTY; } } /* remove the entry */ err = lfs_dir_remove(lfs, &cwd, &entry); if (err) { return err; } /* if we were a directory, find pred, replace tail */ if (entry.d.type == LFS_TYPE_DIR) { int res = lfs_pred(lfs, dir.pair, &cwd); if (res < 0) { return res; } LFS_ASSERT(res); /* must have pred */ cwd.d.tail[0] = dir.d.tail[0]; cwd.d.tail[1] = dir.d.tail[1]; err = lfs_dir_commit(lfs, &cwd, NULL, 0); if (err) { return err; } } return 0; } int lfs_rename(FAR lfs_t *lfs, FAR const char *oldpath, FAR const char *newpath) { lfs_dir_t oldcwd; lfs_dir_t newcwd; lfs_dir_t dir; lfs_entry_t oldentry; lfs_entry_t preventry; lfs_entry_t newentry; bool prevexists; int err; /* deorphan if we haven't yet, needed at most once after poweron */ if (!lfs->deorphaned) { err = lfs_deorphan(lfs); if (err) { return err; } } /* find old entry */ err = lfs_dir_find(lfs, &oldcwd, &oldentry, &(FAR const char *){ oldpath }); if (err) { return err; } /* mark as moving */ oldentry.d.type |= 0x80; err = lfs_dir_update(lfs, &oldcwd, &oldentry, NULL); if (err) { return err; } /* allocate new entry */ err = lfs_dir_find(lfs, &newcwd, &preventry, &newpath); if (err && (err != LFS_ERR_NOENT || strchr(newpath, '/') != NULL)) { return err; } /* must have same type */ prevexists = (err != LFS_ERR_NOENT); if (prevexists && preventry.d.type != (0x7f & oldentry.d.type)) { return LFS_ERR_ISDIR; } if (prevexists && preventry.d.type == LFS_TYPE_DIR) { /* must be empty before removal, checking size * without masking top bit checks for any case where * dir is not empty */ err = lfs_dir_fetch(lfs, &dir, preventry.d.u.dir); if (err) { return err; } else if (dir.d.size != sizeof(dir.d) + 4) { return LFS_ERR_NOTEMPTY; } } /* move to new location */ newentry = preventry; newentry.d = oldentry.d; newentry.d.type &= ~0x80; newentry.d.nlen = strlen(newpath); if (prevexists) { err = lfs_dir_update(lfs, &newcwd, &newentry, newpath); if (err) { return err; } } else { err = lfs_dir_append(lfs, &newcwd, &newentry, newpath); if (err) { return err; } } /* fetch old pair again in case dir block changed */ lfs->moving = true; err = lfs_dir_find(lfs, &oldcwd, &oldentry, &oldpath); if (err) { return err; } lfs->moving = false; /* remove old entry */ err = lfs_dir_remove(lfs, &oldcwd, &oldentry); if (err) { return err; } /* if we were a directory, find pred, replace tail */ if (prevexists && preventry.d.type == LFS_TYPE_DIR) { int res = lfs_pred(lfs, dir.pair, &newcwd); if (res < 0) { return res; } LFS_ASSERT(res); /* must have pred */ newcwd.d.tail[0] = dir.d.tail[0]; newcwd.d.tail[1] = dir.d.tail[1]; err = lfs_dir_commit(lfs, &newcwd, NULL, 0); if (err) { return err; } } return 0; } int lfs_format(FAR lfs_t *lfs, FAR const struct lfs_config_s *cfg) { lfs_superblock_t superblock; bool valid; int err = 0; if (true) { lfs_dir_t superdir; lfs_dir_t root; int i; err = lfs_init(lfs, cfg); if (err) { return err; } /* create free lookahead */ memset(lfs->free.buffer, 0, lfs->cfg->lookahead / 8); lfs->free.off = 0; lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count); lfs->free.i = 0; lfs_alloc_ack(lfs); /* create superblock dir */ err = lfs_dir_alloc(lfs, &superdir); if (err) { goto cleanup; } /* write root directory */ err = lfs_dir_alloc(lfs, &root); if (err) { goto cleanup; } err = lfs_dir_commit(lfs, &root, NULL, 0); if (err) { goto cleanup; } lfs->root[0] = root.pair[0]; lfs->root[1] = root.pair[1]; /* write superblocks */ superblock.off = sizeof(superdir.d); superblock.d.type = LFS_TYPE_SUPERBLOCK; superblock.d.elen = sizeof(superblock.d) - sizeof(superblock.d.magic) - 4; superblock.d.nlen = sizeof(superblock.d.magic); superblock.d.version = LFS_DISK_VERSION; superblock.d.block_size = lfs->cfg->block_size; superblock.d.block_count = lfs->cfg->block_count; superblock.d.root[0] = lfs->root[0]; superblock.d.root[1] = lfs->root[1]; memcpy(superblock.d.magic, "littlefs", 8); superdir.d.tail[0] = root.pair[0]; superdir.d.tail[1] = root.pair[1]; superdir.d.size = sizeof(superdir.d) + sizeof(superblock.d) + 4; /* write both pairs to be safe */ lfs_superblock_tole32(&superblock.d); valid = false; for (i = 0; i < 2; i++) { err = lfs_dir_commit( lfs, &superdir, (struct lfs_region_s[]) { { sizeof(superdir.d), sizeof(superblock.d), &superblock.d, sizeof(superblock.d) } }, 1); if (err && err != LFS_ERR_CORRUPT) { goto cleanup; } valid = valid || !err; } if (!valid) { err = LFS_ERR_CORRUPT; goto cleanup; } /* sanity check that fetch works */ err = lfs_dir_fetch(lfs, &superdir, (FAR const lfs_block_t[2]){0, 1}); if (err) { goto cleanup; } lfs_alloc_ack(lfs); } cleanup: lfs_deinit(lfs); return err; } int lfs_mount(FAR lfs_t *lfs, FAR const struct lfs_config_s *cfg) { int err = 0; if (true) { lfs_dir_t dir; lfs_superblock_t superblock; uint16_t major_version; uint16_t minor_version; err = lfs_init(lfs, cfg); if (err) { return err; } /* setup free lookahead */ lfs->free.off = 0; lfs->free.size = 0; lfs->free.i = 0; lfs_alloc_ack(lfs); /* load superblock */ err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1}); if (err && err != LFS_ERR_CORRUPT) { goto cleanup; } if (!err) { err = lfs_bd_read(lfs, dir.pair[0], sizeof(dir.d), &superblock.d, sizeof(superblock.d)); lfs_superblock_fromle32(&superblock.d); if (err) { goto cleanup; } lfs->root[0] = superblock.d.root[0]; lfs->root[1] = superblock.d.root[1]; } if (err || memcmp(superblock.d.magic, "littlefs", 8) != 0) { LFS_ERROR("Invalid superblock at %d %d", 0, 1); err = LFS_ERR_CORRUPT; goto cleanup; } major_version = (0xffff & (superblock.d.version >> 16)); minor_version = (0xffff & (superblock.d.version >> 0)); if ((major_version != LFS_DISK_VERSION_MAJOR || minor_version > LFS_DISK_VERSION_MINOR)) { LFS_ERROR("Invalid version %d.%d", major_version, minor_version); err = LFS_ERR_INVAL; goto cleanup; } return 0; } cleanup: lfs_deinit(lfs); return err; } int lfs_unmount(FAR lfs_t *lfs) { lfs_deinit(lfs); return 0; } /* Littlefs specific operations */ int lfs_traverse(FAR lfs_t *lfs, CODE int (*cb)(void *, lfs_block_t), FAR void *data) { lfs_dir_t dir; lfs_entry_t entry; lfs_block_t cwd[2] = { 0, 1 }; FAR lfs_file_t *f; if (lfs_pairisnull(lfs->root)) { return 0; } /* iterate over metadata pairs */ while (true) { int err; int i; for (i = 0; i < 2; i++) { err = cb(data, cwd[i]); if (err) { return err; } } err = lfs_dir_fetch(lfs, &dir, cwd); if (err) { return err; } /* iterate over contents */ while (dir.off + sizeof(entry.d) <= (0x7fffffff & dir.d.size) - 4) { err = lfs_bd_read(lfs, dir.pair[0], dir.off, &entry.d, sizeof(entry.d)); lfs_entry_fromle32(&entry.d); if (err) { return err; } dir.off += lfs_entry_size(&entry); if ((0x70 & entry.d.type) == (0x70 & LFS_TYPE_REG)) { err = lfs_ctz_traverse(lfs, &lfs->rcache, NULL, entry.d.u.file.head, entry.d.u.file.size, cb, data); if (err) { return err; } } } cwd[0] = dir.d.tail[0]; cwd[1] = dir.d.tail[1]; if (lfs_pairisnull(cwd)) { break; } } /* iterate over any open files */ for (f = lfs->files; f; f = f->next) { if (f->flags & LFS_F_DIRTY) { int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->head, f->size, cb, data); if (err) { return err; } } if (f->flags & LFS_F_WRITING) { int err = lfs_ctz_traverse(lfs, &lfs->rcache, &f->cache, f->block, f->pos, cb, data); if (err) { return err; } } } return 0; } int lfs_deorphan(FAR lfs_t *lfs) { lfs_dir_t pdir = { .d.size = 0x80000000 }; lfs_dir_t cwd = { .d.tail[0] = 0, .d.tail[1] = 1 }; lfs_size_t i; int err; lfs->deorphaned = true; if (lfs_pairisnull(lfs->root)) { return 0; } /* iterate over all directory directory entries */ for (i = 0; i < lfs->cfg->block_count; i++) { lfs_entry_t entry; if (lfs_pairisnull(cwd.d.tail)) { return 0; } err = lfs_dir_fetch(lfs, &cwd, cwd.d.tail); if (err) { return err; } /* check head blocks for orphans */ if (!(0x80000000 & pdir.d.size)) { lfs_dir_t parent; int res; /* check if we have a parent */ res = lfs_parent(lfs, pdir.d.tail, &parent, &entry); if (res < 0) { return res; } if (!res) { /* we are an orphan */ LFS_DEBUG("Found orphan %" PRIu32 " %" PRIu32, pdir.d.tail[0], pdir.d.tail[1]); pdir.d.tail[0] = cwd.d.tail[0]; pdir.d.tail[1] = cwd.d.tail[1]; err = lfs_dir_commit(lfs, &pdir, NULL, 0); if (err) { return err; } return 0; } if (!lfs_pairsync(entry.d.u.dir, pdir.d.tail)) { /* we have desynced */ LFS_DEBUG("Found desync %" PRIu32 " %" PRIu32, entry.d.u.dir[0], entry.d.u.dir[1]); pdir.d.tail[0] = entry.d.u.dir[0]; pdir.d.tail[1] = entry.d.u.dir[1]; err = lfs_dir_commit(lfs, &pdir, NULL, 0); if (err) { return err; } return 0; } } /* check entries for moves */ while (true) { err = lfs_dir_next(lfs, &cwd, &entry); if (err && err != LFS_ERR_NOENT) { return err; } if (err == LFS_ERR_NOENT) { break; } /* found moved entry */ if (entry.d.type & 0x80) { int moved = lfs_moved(lfs, &entry.d.u); if (moved < 0) { return moved; } if (moved) { LFS_DEBUG("Found move %" PRIu32 " %" PRIu32, entry.d.u.dir[0], entry.d.u.dir[1]); err = lfs_dir_remove(lfs, &cwd, &entry); if (err) { return err; } } else { LFS_DEBUG("Found partial move %" PRIu32 " %" PRIu32, entry.d.u.dir[0], entry.d.u.dir[1]); entry.d.type &= ~0x80; err = lfs_dir_update(lfs, &cwd, &entry, NULL); if (err) { return err; } } } } memcpy(&pdir, &cwd, sizeof(pdir)); } /* If we reached here, we have more directory pairs than blocks in the * filesystem... So something must be horribly wrong */ return LFS_ERR_CORRUPT; }