2017-02-27 00:05:27 +00:00
|
|
|
/*
|
|
|
|
* The little filesystem
|
|
|
|
*
|
2017-10-13 01:27:33 +00:00
|
|
|
* Copyright (c) 2017 ARM Limited
|
|
|
|
*
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
*
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
*
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
* limitations under the License.
|
2017-02-27 00:05:27 +00:00
|
|
|
*/
|
|
|
|
#include "lfs.h"
|
2017-03-25 21:20:31 +00:00
|
|
|
#include "lfs_util.h"
|
2017-02-27 00:05:27 +00:00
|
|
|
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
/// Caching block device operations ///
|
2018-08-04 19:48:27 +00:00
|
|
|
static int lfs_cache_read(lfs_t *lfs,
|
|
|
|
const lfs_cache_t *pcache, lfs_cache_t *rcache, bool store,
|
|
|
|
lfs_block_t block, lfs_off_t off,
|
|
|
|
void *buffer, lfs_size_t size) {
|
2017-04-22 18:30:40 +00:00
|
|
|
uint8_t *data = buffer;
|
2018-03-17 15:28:14 +00:00
|
|
|
LFS_ASSERT(block != 0xffffffff);
|
2017-04-22 18:30:40 +00:00
|
|
|
|
|
|
|
while (size > 0) {
|
2018-08-04 19:48:27 +00:00
|
|
|
if (pcache && block == pcache->block &&
|
|
|
|
off >= pcache->off &&
|
|
|
|
off < pcache->off + pcache->size) {
|
2017-04-30 16:19:37 +00:00
|
|
|
// is already in pcache?
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs_size_t diff = lfs_min(size, pcache->size - (off-pcache->off));
|
2017-04-30 16:19:37 +00:00
|
|
|
memcpy(data, &pcache->buffer[off-pcache->off], diff);
|
2017-04-22 18:30:40 +00:00
|
|
|
|
|
|
|
data += diff;
|
|
|
|
off += diff;
|
|
|
|
size -= diff;
|
|
|
|
continue;
|
2017-04-30 16:19:37 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
if (block == rcache->block &&
|
|
|
|
off >= rcache->off &&
|
|
|
|
off < rcache->off + rcache->size) {
|
2017-04-30 16:19:37 +00:00
|
|
|
// is already in rcache?
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs_size_t diff = lfs_min(size, rcache->size - (off-rcache->off));
|
|
|
|
if (pcache && block == pcache->block) {
|
|
|
|
diff = lfs_min(diff, pcache->off - off);
|
|
|
|
}
|
2017-04-30 16:19:37 +00:00
|
|
|
memcpy(data, &rcache->buffer[off-rcache->off], diff);
|
2017-04-22 18:30:40 +00:00
|
|
|
|
|
|
|
data += diff;
|
|
|
|
off += diff;
|
|
|
|
size -= diff;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
if (!store && off % lfs->cfg->read_size == 0 &&
|
|
|
|
size >= lfs->cfg->read_size) {
|
2017-04-22 18:30:40 +00:00
|
|
|
// bypass cache?
|
|
|
|
lfs_size_t diff = size - (size % lfs->cfg->read_size);
|
2017-04-24 04:49:21 +00:00
|
|
|
int err = lfs->cfg->read(lfs->cfg, block, off, data, diff);
|
2017-04-22 18:30:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
data += diff;
|
|
|
|
off += diff;
|
|
|
|
size -= diff;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// load to cache, first condition can no longer fail
|
2018-03-17 15:28:14 +00:00
|
|
|
LFS_ASSERT(block < lfs->cfg->block_count);
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs_size_t size = store ? lfs->cfg->cache_size : lfs->cfg->prog_size;
|
2017-04-30 16:19:37 +00:00
|
|
|
rcache->block = block;
|
2018-08-04 19:48:27 +00:00
|
|
|
rcache->off = lfs_aligndown(off, size);
|
|
|
|
rcache->size = size;
|
2017-04-30 16:19:37 +00:00
|
|
|
int err = lfs->cfg->read(lfs->cfg, rcache->block,
|
2018-08-04 19:48:27 +00:00
|
|
|
rcache->off, rcache->buffer, size);
|
2017-04-22 18:30:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2017-03-25 21:20:31 +00:00
|
|
|
}
|
2017-02-27 00:05:27 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
static int lfs_cache_cmp(lfs_t *lfs,
|
|
|
|
const lfs_cache_t *pcache, lfs_cache_t *rcache,
|
|
|
|
lfs_block_t block, lfs_off_t off,
|
|
|
|
const void *buffer, lfs_size_t size) {
|
2017-06-24 05:43:05 +00:00
|
|
|
const uint8_t *data = buffer;
|
|
|
|
|
|
|
|
for (lfs_off_t i = 0; i < size; i++) {
|
|
|
|
uint8_t c;
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_read(lfs, pcache, rcache, true,
|
2017-06-24 05:43:05 +00:00
|
|
|
block, off+i, &c, 1);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (c != data[i]) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
static int lfs_cache_crc(lfs_t *lfs,
|
|
|
|
const lfs_cache_t *pcache, lfs_cache_t *rcache,
|
|
|
|
lfs_block_t block, lfs_off_t off, lfs_size_t size, uint32_t *crc) {
|
2017-06-24 05:43:05 +00:00
|
|
|
for (lfs_off_t i = 0; i < size; i++) {
|
|
|
|
uint8_t c;
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_read(lfs, pcache, rcache, true,
|
2017-06-24 05:43:05 +00:00
|
|
|
block, off+i, &c, 1);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
lfs_crc(crc, &c, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lfs_cache_flush(lfs_t *lfs,
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate) {
|
2017-06-24 05:43:05 +00:00
|
|
|
if (pcache->block != 0xffffffff) {
|
2018-03-17 15:28:14 +00:00
|
|
|
LFS_ASSERT(pcache->block < lfs->cfg->block_count);
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs_size_t diff = lfs_alignup(pcache->size, lfs->cfg->prog_size);
|
2017-06-24 05:43:05 +00:00
|
|
|
int err = lfs->cfg->prog(lfs->cfg, pcache->block,
|
2018-08-04 19:48:27 +00:00
|
|
|
pcache->off, pcache->buffer, diff);
|
2017-04-30 16:19:37 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-04-22 18:30:40 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
if (validate) {
|
|
|
|
// check data on disk
|
|
|
|
rcache->block = 0xffffffff;
|
|
|
|
int res = lfs_cache_cmp(lfs, NULL, rcache, pcache->block,
|
|
|
|
pcache->off, pcache->buffer, diff);
|
2017-06-24 05:43:05 +00:00
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!res) {
|
|
|
|
return LFS_ERR_CORRUPT;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pcache->block = 0xffffffff;
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
static int lfs_cache_prog(lfs_t *lfs,
|
|
|
|
lfs_cache_t *pcache, lfs_cache_t *rcache, bool validate,
|
|
|
|
lfs_block_t block, lfs_off_t off,
|
|
|
|
const void *buffer, lfs_size_t size) {
|
2017-04-30 16:19:37 +00:00
|
|
|
const uint8_t *data = buffer;
|
2018-03-17 15:28:14 +00:00
|
|
|
LFS_ASSERT(block != 0xffffffff);
|
Added internal lfs_dir_set, an umbrella to dir append/update/remove operations
This move was surprisingly complex, but offers the ultimate opportunity for
code reuse in terms of resizable entries. Instead of needing to provide
separate functions for adding and removing entries, adding and removing
entries can just be viewed as changing an entry's size to-and-from zero.
Unfortunately, it's not _quite_ that simple, since append and remove
hide some relatively complex operations for when directory blocks
overflow or need to be cleaned up.
However, with enough shoehorning, and a new committer type that allows
specifying recursive commit lists (is this now a push-down automata?),
it does seem to be possible to shove all of the entry update logic into
a single function.
Sidenote, I switched back to an enum-based DSL, since the addition of a
recursive region opcode breaks the consistency of what needs to be
passed to the DSL callback functions. It's much simpler to handle each
opcode explicitly inside a recursive lfs_commit_region function.
2018-03-27 22:57:07 +00:00
|
|
|
LFS_ASSERT(off + size <= lfs->cfg->block_size);
|
2017-04-30 16:19:37 +00:00
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
while (size > 0) {
|
2018-08-04 19:48:27 +00:00
|
|
|
if (block == pcache->block &&
|
|
|
|
off >= pcache->off &&
|
|
|
|
off < pcache->off + lfs->cfg->cache_size) {
|
|
|
|
// already fits in pcache?
|
2017-04-22 18:30:40 +00:00
|
|
|
lfs_size_t diff = lfs_min(size,
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs->cfg->cache_size - (off-pcache->off));
|
2017-06-24 05:43:05 +00:00
|
|
|
memcpy(&pcache->buffer[off-pcache->off], data, diff);
|
2017-04-22 18:30:40 +00:00
|
|
|
|
|
|
|
data += diff;
|
|
|
|
off += diff;
|
|
|
|
size -= diff;
|
2017-04-30 16:19:37 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
pcache->size = off - pcache->off;
|
|
|
|
if (pcache->size == lfs->cfg->cache_size) {
|
2017-06-24 05:43:05 +00:00
|
|
|
// eagerly flush out pcache if we fill up
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_flush(lfs, pcache, rcache, validate);
|
2017-04-30 16:19:37 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-06-24 05:43:05 +00:00
|
|
|
// pcache must have been flushed, either by programming and
|
|
|
|
// entire block or manually flushing the pcache
|
2018-01-29 21:20:12 +00:00
|
|
|
LFS_ASSERT(pcache->block == 0xffffffff);
|
2017-04-22 18:30:40 +00:00
|
|
|
|
2017-06-24 05:43:05 +00:00
|
|
|
// prepare pcache, first condition can no longer fail
|
|
|
|
pcache->block = block;
|
2018-08-04 19:48:27 +00:00
|
|
|
pcache->off = lfs_aligndown(off, lfs->cfg->prog_size);
|
|
|
|
pcache->size = 0;
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
2017-03-25 21:20:31 +00:00
|
|
|
}
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
|
|
|
|
/// General lfs block device operations ///
|
2017-06-24 05:43:05 +00:00
|
|
|
static int lfs_bd_read(lfs_t *lfs, lfs_block_t block,
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_off_t off, void *buffer, lfs_size_t size) {
|
2018-08-04 19:48:27 +00:00
|
|
|
return lfs_cache_read(lfs, &lfs->pcache, &lfs->rcache, true,
|
2017-04-30 16:19:37 +00:00
|
|
|
block, off, buffer, size);
|
|
|
|
}
|
|
|
|
|
2017-06-24 05:43:05 +00:00
|
|
|
static int lfs_bd_prog(lfs_t *lfs, lfs_block_t block,
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_off_t off, const void *buffer, lfs_size_t size) {
|
2018-08-04 19:48:27 +00:00
|
|
|
return lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, false,
|
2017-04-30 16:19:37 +00:00
|
|
|
block, off, buffer, size);
|
|
|
|
}
|
|
|
|
|
2017-06-24 05:43:05 +00:00
|
|
|
static int lfs_bd_cmp(lfs_t *lfs, lfs_block_t block,
|
|
|
|
lfs_off_t off, const void *buffer, lfs_size_t size) {
|
2018-08-04 19:48:27 +00:00
|
|
|
return lfs_cache_cmp(lfs, NULL, &lfs->rcache, block, off, buffer, size);
|
2017-06-24 05:43:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int lfs_bd_crc(lfs_t *lfs, lfs_block_t block,
|
|
|
|
lfs_off_t off, lfs_size_t size, uint32_t *crc) {
|
2018-08-04 19:48:27 +00:00
|
|
|
return lfs_cache_crc(lfs, NULL, &lfs->rcache, block, off, size, crc);
|
2017-06-24 05:43:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int lfs_bd_erase(lfs_t *lfs, lfs_block_t block) {
|
2018-03-17 15:28:14 +00:00
|
|
|
LFS_ASSERT(block < lfs->cfg->block_count);
|
2017-04-22 16:42:05 +00:00
|
|
|
return lfs->cfg->erase(lfs->cfg, block);
|
2017-03-25 21:20:31 +00:00
|
|
|
}
|
2017-02-27 00:05:27 +00:00
|
|
|
|
2017-06-24 05:43:05 +00:00
|
|
|
static int lfs_bd_sync(lfs_t *lfs) {
|
2017-06-25 21:21:14 +00:00
|
|
|
lfs->rcache.block = 0xffffffff;
|
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_flush(lfs, &lfs->pcache, &lfs->rcache, false);
|
2017-04-22 18:30:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-04-22 16:42:05 +00:00
|
|
|
return lfs->cfg->sync(lfs->cfg);
|
2017-02-27 00:05:27 +00:00
|
|
|
}
|
|
|
|
|
2017-03-25 21:20:31 +00:00
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
/// Internal operations predeclared here ///
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_pred(lfs_t *lfs, const lfs_block_t dir[2],
|
|
|
|
lfs_mdir_t *pdir);
|
|
|
|
static int32_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t dir[2],
|
2018-07-13 01:22:06 +00:00
|
|
|
lfs_mdir_t *parent);
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_relocate(lfs_t *lfs,
|
2018-08-01 23:10:24 +00:00
|
|
|
const lfs_block_t oldpair[2], lfs_block_t newpair[2]);
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_forceconsistency(lfs_t *lfs);
|
2017-05-14 17:01:45 +00:00
|
|
|
|
|
|
|
|
2017-03-25 21:20:31 +00:00
|
|
|
/// Block allocator ///
|
2018-07-30 19:40:27 +00:00
|
|
|
static int lfs_alloc_lookahead(void *p, lfs_block_t block) {
|
|
|
|
lfs_t *lfs = (lfs_t*)p;
|
2018-04-10 20:14:27 +00:00
|
|
|
lfs_block_t off = ((block - lfs->free.off)
|
2017-09-17 19:52:25 +00:00
|
|
|
+ lfs->cfg->block_count) % lfs->cfg->block_count;
|
|
|
|
|
2018-02-08 07:30:21 +00:00
|
|
|
if (off < lfs->free.size) {
|
2017-09-19 02:20:33 +00:00
|
|
|
lfs->free.buffer[off / 32] |= 1U << (off % 32);
|
2017-04-01 15:44:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
static int lfs_alloc(lfs_t *lfs, lfs_block_t *block) {
|
2017-04-01 15:44:17 +00:00
|
|
|
while (true) {
|
2018-04-10 20:14:27 +00:00
|
|
|
while (lfs->free.i != lfs->free.size) {
|
|
|
|
lfs_block_t off = lfs->free.i;
|
|
|
|
lfs->free.i += 1;
|
|
|
|
lfs->free.ack -= 1;
|
2017-04-22 19:56:12 +00:00
|
|
|
|
2017-09-19 02:20:33 +00:00
|
|
|
if (!(lfs->free.buffer[off / 32] & (1U << (off % 32)))) {
|
2017-04-22 19:56:12 +00:00
|
|
|
// found a free block
|
2018-04-10 20:14:27 +00:00
|
|
|
*block = (lfs->free.off + off) % lfs->cfg->block_count;
|
2018-04-06 22:00:29 +00:00
|
|
|
|
|
|
|
// eagerly find next off so an alloc ack can
|
|
|
|
// discredit old lookahead blocks
|
2018-04-10 20:14:27 +00:00
|
|
|
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;
|
2018-04-06 22:00:29 +00:00
|
|
|
}
|
|
|
|
|
2017-04-22 19:56:12 +00:00
|
|
|
return 0;
|
2017-04-01 15:44:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-08 07:30:21 +00:00
|
|
|
// check if we have looked at all blocks since last ack
|
2018-04-10 20:14:27 +00:00
|
|
|
if (lfs->free.ack == 0) {
|
|
|
|
LFS_WARN("No more free space %d", lfs->free.i + lfs->free.off);
|
2018-02-08 07:30:21 +00:00
|
|
|
return LFS_ERR_NOSPC;
|
|
|
|
}
|
|
|
|
|
2018-04-10 20:14:27 +00:00
|
|
|
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;
|
2017-04-01 15:44:17 +00:00
|
|
|
|
2017-04-22 19:56:12 +00:00
|
|
|
// find mask of free blocks from tree
|
2017-11-10 01:10:08 +00:00
|
|
|
memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
|
2018-07-30 19:40:27 +00:00
|
|
|
int err = lfs_fs_traverse(lfs, lfs_alloc_lookahead, lfs);
|
2017-04-01 15:44:17 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-04-01 17:23:15 +00:00
|
|
|
}
|
2017-03-25 21:20:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
static void lfs_alloc_ack(lfs_t *lfs) {
|
2018-04-10 20:14:27 +00:00
|
|
|
lfs->free.ack = lfs->cfg->block_count;
|
2017-03-25 21:20:31 +00:00
|
|
|
}
|
2017-03-13 00:41:08 +00:00
|
|
|
|
2017-02-27 00:05:27 +00:00
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
/// Metadata pair and directory operations ///
|
2017-04-01 15:44:17 +00:00
|
|
|
static inline void lfs_pairswap(lfs_block_t pair[2]) {
|
2017-03-25 21:20:31 +00:00
|
|
|
lfs_block_t t = pair[0];
|
|
|
|
pair[0] = pair[1];
|
|
|
|
pair[1] = t;
|
2017-03-12 20:11:52 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
static inline bool lfs_pairisnull(const lfs_block_t pair[2]) {
|
2017-04-30 16:54:27 +00:00
|
|
|
return pair[0] == 0xffffffff || pair[1] == 0xffffffff;
|
2017-04-18 03:27:06 +00:00
|
|
|
}
|
|
|
|
|
2017-04-01 15:44:17 +00:00
|
|
|
static inline int lfs_paircmp(
|
|
|
|
const lfs_block_t paira[2],
|
|
|
|
const lfs_block_t pairb[2]) {
|
2017-04-29 17:41:53 +00:00
|
|
|
return !(paira[0] == pairb[0] || paira[1] == pairb[1] ||
|
|
|
|
paira[0] == pairb[1] || paira[1] == pairb[0]);
|
2017-04-01 15:44:17 +00:00
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
static inline bool lfs_pairsync(
|
|
|
|
const lfs_block_t paira[2],
|
|
|
|
const lfs_block_t pairb[2]) {
|
|
|
|
return (paira[0] == pairb[0] && paira[1] == pairb[1]) ||
|
|
|
|
(paira[0] == pairb[1] && paira[1] == pairb[0]);
|
|
|
|
}
|
|
|
|
|
2018-08-01 23:10:24 +00:00
|
|
|
static inline void lfs_pairfromle32(lfs_block_t *pair) {
|
|
|
|
pair[0] = lfs_fromle32(pair[0]);
|
|
|
|
pair[1] = lfs_fromle32(pair[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void lfs_pairtole32(lfs_block_t *pair) {
|
|
|
|
pair[0] = lfs_tole32(pair[0]);
|
|
|
|
pair[1] = lfs_tole32(pair[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lfs_ctzfromle32(struct lfs_ctz *ctz) {
|
|
|
|
ctz->head = lfs_fromle32(ctz->head);
|
|
|
|
ctz->size = lfs_fromle32(ctz->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void lfs_ctztole32(struct lfs_ctz *ctz) {
|
|
|
|
ctz->head = lfs_tole32(ctz->head);
|
|
|
|
ctz->size = lfs_tole32(ctz->size);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
/// Entry tag operations ///
|
2018-07-13 00:07:56 +00:00
|
|
|
#define LFS_MKTAG(type, id, size) \
|
|
|
|
(((uint32_t)(type) << 22) | ((uint32_t)(id) << 12) | (uint32_t)(size))
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
#define LFS_MKATTR(type, id, buffer, size, next) \
|
2018-07-29 20:03:23 +00:00
|
|
|
&(const lfs_mattr_t){LFS_MKTAG(type, id, size), (buffer), (next)}
|
2018-05-19 23:25:47 +00:00
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline bool lfs_tagisvalid(uint32_t tag) {
|
2018-05-19 23:25:47 +00:00
|
|
|
return !(tag & 0x80000000);
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline bool lfs_tagisuser(uint32_t tag) {
|
2018-07-09 19:13:31 +00:00
|
|
|
return (tag & 0x40000000);
|
2018-05-19 23:25:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline uint16_t lfs_tagtype(uint32_t tag) {
|
2018-07-09 19:13:31 +00:00
|
|
|
return (tag & 0x7fc00000) >> 22;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline uint16_t lfs_tagsubtype(uint32_t tag) {
|
2018-07-09 19:13:31 +00:00
|
|
|
return (tag & 0x7c000000) >> 22;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline uint16_t lfs_tagid(uint32_t tag) {
|
2018-05-30 01:08:42 +00:00
|
|
|
return (tag & 0x003ff000) >> 12;
|
2018-05-19 23:25:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static inline lfs_size_t lfs_tagsize(uint32_t tag) {
|
2018-05-19 23:25:47 +00:00
|
|
|
return tag & 0x00000fff;
|
|
|
|
}
|
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// operations on set of globals
|
|
|
|
static inline void lfs_globalxor(lfs_global_t *a, const lfs_global_t *b) {
|
2018-08-04 00:01:27 +00:00
|
|
|
for (int i = 0; i < sizeof(lfs_global_t)/4; i++) {
|
|
|
|
a->u32[i] ^= b->u32[i];
|
2018-07-31 13:07:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool lfs_globaliszero(const lfs_global_t *a) {
|
2018-08-04 00:01:27 +00:00
|
|
|
for (int i = 0; i < sizeof(lfs_global_t)/4; i++) {
|
|
|
|
if (a->u32[i] != 0) {
|
2018-07-31 13:07:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
static inline void lfs_globalzero(lfs_global_t *a) {
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs_globalxor(a, a);
|
2018-07-31 13:07:36 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 23:10:24 +00:00
|
|
|
static inline void lfs_globalfromle32(lfs_global_t *a) {
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs_pairfromle32(a->s.movepair);
|
|
|
|
a->s.moveid = lfs_fromle16(a->s.moveid);
|
2018-08-01 23:10:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void lfs_globaltole32(lfs_global_t *a) {
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs_pairtole32(a->s.movepair);
|
|
|
|
a->s.moveid = lfs_tole16(a->s.moveid);
|
2018-07-31 13:07:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static inline void lfs_globalmove(lfs_t *lfs,
|
|
|
|
const lfs_block_t pair[2], uint16_t id) {
|
|
|
|
lfs_global_t diff;
|
|
|
|
lfs_globalzero(&diff);
|
2018-08-04 00:01:27 +00:00
|
|
|
diff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ pair[0];
|
|
|
|
diff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ pair[1];
|
|
|
|
diff.s.moveid ^= lfs->globals.s.moveid ^ id;
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_globalfromle32(&lfs->locals);
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &diff);
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_globaltole32(&lfs->locals);
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->globals, &diff);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void lfs_globaldeorphaned(lfs_t *lfs, bool deorphaned) {
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs->locals.s.deorphaned ^= lfs->globals.s.deorphaned ^ deorphaned;
|
|
|
|
lfs->globals.s.deorphaned ^= lfs->globals.s.deorphaned ^ deorphaned;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// commit logic
|
2018-05-19 23:25:47 +00:00
|
|
|
struct lfs_commit {
|
|
|
|
lfs_block_t block;
|
|
|
|
lfs_off_t off;
|
2018-07-13 01:22:06 +00:00
|
|
|
uint32_t ptag;
|
2018-05-19 23:25:47 +00:00
|
|
|
uint32_t crc;
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
lfs_off_t begin;
|
|
|
|
lfs_off_t end;
|
2018-05-19 23:25:47 +00:00
|
|
|
};
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
struct lfs_diskoff {
|
|
|
|
lfs_block_t block;
|
|
|
|
lfs_off_t off;
|
|
|
|
};
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int32_t lfs_commitget(lfs_t *lfs, lfs_block_t block, lfs_off_t off,
|
2018-07-17 23:31:30 +00:00
|
|
|
uint32_t tag, uint32_t getmask, uint32_t gettag, int32_t getdiff,
|
2018-07-13 20:04:31 +00:00
|
|
|
void *buffer, bool stopatcommit) {
|
|
|
|
// iterate over dir block backwards (for faster lookups)
|
2018-07-30 19:23:51 +00:00
|
|
|
while (off >= 2*sizeof(tag)+lfs_tagsize(tag)) {
|
2018-07-13 20:04:31 +00:00
|
|
|
off -= sizeof(tag)+lfs_tagsize(tag);
|
|
|
|
|
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_CRC && stopatcommit) {
|
|
|
|
break;
|
|
|
|
} else if (lfs_tagtype(tag) == LFS_TYPE_DELETE) {
|
2018-07-17 23:31:30 +00:00
|
|
|
if (lfs_tagid(tag) <= lfs_tagid(gettag + getdiff)) {
|
|
|
|
getdiff += LFS_MKTAG(0, 1, 0);
|
2018-07-13 20:04:31 +00:00
|
|
|
}
|
2018-07-17 23:31:30 +00:00
|
|
|
} else if ((tag & getmask) == ((gettag + getdiff) & getmask)) {
|
2018-07-13 20:04:31 +00:00
|
|
|
if (buffer) {
|
|
|
|
lfs_size_t diff = lfs_min(
|
|
|
|
lfs_tagsize(gettag), lfs_tagsize(tag));
|
|
|
|
int err = lfs_bd_read(lfs, block,
|
|
|
|
off+sizeof(tag), buffer, diff);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
memset((uint8_t*)buffer + diff, 0,
|
|
|
|
lfs_tagsize(gettag) - diff);
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
return tag - getdiff;
|
2018-07-13 20:04:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t ntag;
|
|
|
|
int err = lfs_bd_read(lfs, block, off, &ntag, sizeof(ntag));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
tag ^= lfs_fromle32(ntag);
|
|
|
|
}
|
|
|
|
|
|
|
|
return LFS_ERR_NOENT;
|
|
|
|
}
|
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
static int lfs_commitattrs(lfs_t *lfs, struct lfs_commit *commit,
|
|
|
|
uint16_t id, const struct lfs_attr *attrs);
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_commitmove(lfs_t *lfs, struct lfs_commit *commit,
|
2018-05-28 14:17:44 +00:00
|
|
|
uint16_t fromid, uint16_t toid,
|
2018-07-13 01:43:55 +00:00
|
|
|
const lfs_mdir_t *dir, const lfs_mattr_t *attrs);
|
2018-05-28 07:08:16 +00:00
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_commitattr(lfs_t *lfs, struct lfs_commit *commit,
|
2018-07-13 01:43:55 +00:00
|
|
|
uint32_t tag, const void *buffer) {
|
2018-07-29 20:03:23 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_FROM_ATTRS) {
|
|
|
|
// special case for custom attributes
|
|
|
|
return lfs_commitattrs(lfs, commit,
|
|
|
|
lfs_tagid(tag), buffer);
|
|
|
|
} else if (lfs_tagtype(tag) == LFS_FROM_MOVE) {
|
2018-07-13 20:04:31 +00:00
|
|
|
// special case for moves
|
|
|
|
return lfs_commitmove(lfs, commit,
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_tagsize(tag), lfs_tagid(tag),
|
|
|
|
buffer, NULL);
|
2018-05-28 07:08:16 +00:00
|
|
|
}
|
|
|
|
|
2018-05-19 23:25:47 +00:00
|
|
|
// check if we fit
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_size_t size = lfs_tagsize(tag);
|
2018-07-13 20:04:31 +00:00
|
|
|
if (commit->off + sizeof(tag)+size > commit->end) {
|
2018-05-19 23:25:47 +00:00
|
|
|
return LFS_ERR_NOSPC;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write out tag
|
2018-07-13 01:43:55 +00:00
|
|
|
uint32_t ntag = lfs_tole32((tag & 0x7fffffff) ^ commit->ptag);
|
|
|
|
lfs_crc(&commit->crc, &ntag, sizeof(ntag));
|
|
|
|
int err = lfs_bd_prog(lfs, commit->block, commit->off,
|
|
|
|
&ntag, sizeof(ntag));
|
2018-05-19 23:25:47 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-07-13 01:43:55 +00:00
|
|
|
commit->off += sizeof(ntag);
|
2018-05-19 23:25:47 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (!(tag & 0x80000000)) {
|
2018-05-19 23:25:47 +00:00
|
|
|
// from memory
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_crc(&commit->crc, buffer, size);
|
|
|
|
err = lfs_bd_prog(lfs, commit->block, commit->off, buffer, size);
|
2018-05-19 23:25:47 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// from disk
|
2018-07-13 01:43:55 +00:00
|
|
|
const struct lfs_diskoff *disk = buffer;
|
2018-05-19 23:25:47 +00:00
|
|
|
for (lfs_off_t i = 0; i < size; i++) {
|
2018-07-13 20:04:31 +00:00
|
|
|
// rely on caching to make this efficient
|
2018-05-19 23:25:47 +00:00
|
|
|
uint8_t dat;
|
2018-07-13 01:43:55 +00:00
|
|
|
int err = lfs_bd_read(lfs, disk->block, disk->off+i, &dat, 1);
|
2018-05-19 23:25:47 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
commit->off += size;
|
|
|
|
commit->ptag = tag & 0x7fffffff;
|
2018-05-19 23:25:47 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
static int lfs_commitattrs(lfs_t *lfs, struct lfs_commit *commit,
|
|
|
|
uint16_t id, const struct lfs_attr *attrs) {
|
|
|
|
for (const struct lfs_attr *a = attrs; a; a = a->next) {
|
|
|
|
int err = lfs_commitattr(lfs, commit,
|
|
|
|
LFS_MKTAG(0x100 | a->type, id, a->size), a->buffer);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_commitmove(lfs_t *lfs, struct lfs_commit *commit,
|
2018-07-12 23:11:18 +00:00
|
|
|
uint16_t fromid, uint16_t toid,
|
2018-07-13 01:43:55 +00:00
|
|
|
const lfs_mdir_t *dir, const lfs_mattr_t *attrs) {
|
2018-07-13 20:04:31 +00:00
|
|
|
// iterate through list and commits, only committing unique entries
|
|
|
|
lfs_off_t off = dir->off;
|
2018-07-13 01:22:06 +00:00
|
|
|
uint32_t ntag = dir->etag;
|
2018-07-13 20:04:31 +00:00
|
|
|
while (attrs || off > sizeof(uint32_t)) {
|
2018-07-13 01:43:55 +00:00
|
|
|
struct lfs_diskoff disk;
|
|
|
|
uint32_t tag;
|
|
|
|
const void *buffer;
|
|
|
|
if (attrs) {
|
|
|
|
tag = attrs->tag;
|
|
|
|
buffer = attrs->buffer;
|
|
|
|
attrs = attrs->next;
|
2018-07-12 23:11:18 +00:00
|
|
|
} else {
|
2018-07-13 20:04:31 +00:00
|
|
|
LFS_ASSERT(off > sizeof(ntag)+lfs_tagsize(ntag));
|
2018-07-13 00:07:56 +00:00
|
|
|
off -= sizeof(ntag)+lfs_tagsize(ntag);
|
2018-05-26 00:04:01 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
tag = ntag;
|
|
|
|
buffer = &disk;
|
2018-07-13 20:04:31 +00:00
|
|
|
disk.block = dir->pair[0];
|
|
|
|
disk.off = off + sizeof(tag);
|
2018-05-26 00:04:01 +00:00
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_bd_read(lfs, dir->pair[0], off, &ntag, sizeof(ntag));
|
2018-07-12 23:11:18 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-26 00:04:01 +00:00
|
|
|
|
2018-07-12 23:11:18 +00:00
|
|
|
ntag = lfs_fromle32(ntag);
|
2018-07-13 01:43:55 +00:00
|
|
|
ntag ^= tag;
|
|
|
|
tag |= 0x80000000;
|
2018-07-12 23:11:18 +00:00
|
|
|
}
|
2018-05-26 00:04:01 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_DELETE && lfs_tagid(tag) <= fromid) {
|
2018-07-12 23:11:18 +00:00
|
|
|
// something was deleted, we need to move around it
|
|
|
|
fromid += 1;
|
2018-07-13 01:43:55 +00:00
|
|
|
} else if (lfs_tagid(tag) != fromid) {
|
2018-07-12 23:11:18 +00:00
|
|
|
// ignore non-matching ids
|
|
|
|
} else {
|
|
|
|
// check if type has already been committed
|
2018-07-13 20:04:31 +00:00
|
|
|
int32_t res = lfs_commitget(lfs, commit->block,
|
2018-07-13 01:43:55 +00:00
|
|
|
commit->off, commit->ptag,
|
|
|
|
lfs_tagisuser(tag) ? 0x7ffff000 : 0x7c3ff000,
|
2018-07-13 20:04:31 +00:00
|
|
|
LFS_MKTAG(lfs_tagtype(tag), toid, 0),
|
|
|
|
0, NULL, true);
|
2018-07-13 01:22:06 +00:00
|
|
|
if (res < 0 && res != LFS_ERR_NOENT) {
|
|
|
|
return res;
|
2018-07-12 23:11:18 +00:00
|
|
|
}
|
2018-05-26 00:04:01 +00:00
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
if (res == LFS_ERR_NOENT) {
|
2018-07-12 23:11:18 +00:00
|
|
|
// update id and commit, as we are currently unique
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_commitattr(lfs, commit,
|
|
|
|
(tag & 0xffc00fff) | LFS_MKTAG(0, toid, 0),
|
2018-07-13 01:43:55 +00:00
|
|
|
buffer);
|
2018-07-12 23:11:18 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
2018-05-28 14:17:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-28 07:08:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_commitglobals(lfs_t *lfs, struct lfs_commit *commit,
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_global_t *locals) {
|
|
|
|
if (lfs_globaliszero(&lfs->locals)) {
|
2018-07-04 06:35:04 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2018-07-02 03:29:42 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(locals, &lfs->locals);
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_commitattr(lfs, commit,
|
2018-08-04 00:01:27 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_GLOBALS + locals->s.deorphaned,
|
|
|
|
0x3ff, sizeof(lfs_global_t)), locals);
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(locals, &lfs->locals);
|
2018-07-13 20:04:31 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lfs_commitcrc(lfs_t *lfs, struct lfs_commit *commit) {
|
|
|
|
// align to program units
|
|
|
|
lfs_off_t off = 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, off, &tag, sizeof(tag));
|
2018-07-04 06:35:04 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// build crc tag
|
2018-08-01 23:10:24 +00:00
|
|
|
tag = lfs_fromle32(tag);
|
|
|
|
tag = (0x80000000 & ~tag) |
|
2018-07-13 20:04:31 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_CRC, 0x3ff,
|
|
|
|
off - (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(tag)+lfs_tagsize(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_tagsize(tag)-commit->begin, &crc);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (crc != commit->crc) {
|
|
|
|
return LFS_ERR_CORRUPT;
|
|
|
|
}
|
|
|
|
|
2018-07-02 03:29:42 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// internal dir operations
|
2018-08-01 15:24:59 +00:00
|
|
|
static int lfs_dir_alloc(lfs_t *lfs, lfs_mdir_t *dir) {
|
2018-05-19 23:25:47 +00:00
|
|
|
// 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;
|
2018-08-01 15:24:59 +00:00
|
|
|
dir->tail[0] = 0xffffffff;
|
|
|
|
dir->tail[1] = 0xffffffff;
|
2018-05-21 05:56:20 +00:00
|
|
|
dir->erased = false;
|
2018-08-01 15:24:59 +00:00
|
|
|
dir->split = false;
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalzero(&dir->locals);
|
2018-05-19 23:25:47 +00:00
|
|
|
|
|
|
|
// don't write out yet, let caller take care of that
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
static int32_t lfs_dir_find(lfs_t *lfs,
|
|
|
|
lfs_mdir_t *dir, const lfs_block_t pair[2],
|
|
|
|
uint32_t findmask, uint32_t findtag,
|
|
|
|
const void *findbuffer) {
|
|
|
|
dir->pair[0] = pair[0];
|
|
|
|
dir->pair[1] = pair[1];
|
|
|
|
int32_t foundtag = LFS_ERR_NOENT;
|
|
|
|
|
|
|
|
// 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]));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
rev[i] = lfs_fromle32(rev[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
dir->off = 0;
|
|
|
|
|
|
|
|
uint32_t tempfoundtag = foundtag;
|
|
|
|
uint16_t tempcount = 0;
|
|
|
|
lfs_block_t temptail[2] = {0xffffffff, 0xffffffff};
|
|
|
|
bool tempsplit = false;
|
|
|
|
lfs_global_t templocals;
|
|
|
|
lfs_globalzero(&templocals);
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// next commit not yet programmed
|
|
|
|
if (lfs_tagtype(ptag) == LFS_TYPE_CRC && !lfs_tagisvalid(tag)) {
|
|
|
|
dir->erased = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// check we're in valid range
|
|
|
|
if (off + sizeof(tag)+lfs_tagsize(tag) > lfs->cfg->block_size) {
|
|
|
|
dir->erased = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_CRC) {
|
|
|
|
// check the crc attr
|
|
|
|
uint32_t dcrc;
|
|
|
|
int err = lfs_bd_read(lfs, dir->pair[0],
|
|
|
|
off+sizeof(tag), &dcrc, sizeof(dcrc));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
dcrc = lfs_fromle32(dcrc);
|
|
|
|
|
|
|
|
if (crc != dcrc) {
|
|
|
|
dir->erased = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
foundtag = tempfoundtag;
|
|
|
|
dir->off = off + sizeof(tag)+lfs_tagsize(tag);
|
|
|
|
dir->etag = tag;
|
|
|
|
dir->count = tempcount;
|
|
|
|
dir->tail[0] = temptail[0];
|
|
|
|
dir->tail[1] = temptail[1];
|
|
|
|
dir->split = tempsplit;
|
|
|
|
dir->locals = templocals;
|
|
|
|
crc = 0xffffffff;
|
|
|
|
} else {
|
|
|
|
err = lfs_bd_crc(lfs, dir->pair[0],
|
|
|
|
off+sizeof(tag), lfs_tagsize(tag), &crc);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lfs_tagid(tag) < 0x3ff && lfs_tagid(tag) >= tempcount) {
|
|
|
|
tempcount = lfs_tagid(tag)+1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lfs_tagsubtype(tag) == LFS_TYPE_TAIL) {
|
|
|
|
tempsplit = (lfs_tagtype(tag) & 1);
|
|
|
|
err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag),
|
|
|
|
temptail, sizeof(temptail));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
lfs_pairfromle32(temptail);
|
|
|
|
} else if (lfs_tagsubtype(tag) == LFS_TYPE_GLOBALS) {
|
2018-08-04 00:01:27 +00:00
|
|
|
templocals.s.deorphaned = (lfs_tagtype(tag) & 1);
|
2018-08-01 15:24:59 +00:00
|
|
|
err = lfs_bd_read(lfs, dir->pair[0], off+sizeof(tag),
|
|
|
|
&templocals, sizeof(templocals));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} else if (lfs_tagsubtype(tag) == LFS_TYPE_DELETE) {
|
|
|
|
LFS_ASSERT(tempcount > 0);
|
|
|
|
tempcount -= 1;
|
|
|
|
|
|
|
|
if (lfs_tagid(tag) == lfs_tagid(tempfoundtag)) {
|
|
|
|
tempfoundtag = LFS_ERR_NOENT;
|
|
|
|
} else if (lfs_tagisvalid(tempfoundtag) &&
|
|
|
|
lfs_tagid(tag) < lfs_tagid(tempfoundtag)) {
|
|
|
|
tempfoundtag -= LFS_MKTAG(0, 1, 0);
|
|
|
|
}
|
|
|
|
} else if ((tag & findmask) == (findtag & findmask)) {
|
|
|
|
int res = lfs_bd_cmp(lfs, dir->pair[0], off+sizeof(tag),
|
|
|
|
findbuffer, lfs_tagsize(tag));
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (res) {
|
|
|
|
// found a match
|
|
|
|
tempfoundtag = tag;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ptag = tag;
|
|
|
|
off += sizeof(tag)+lfs_tagsize(tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
// consider what we have good enough
|
|
|
|
if (dir->off > 0) {
|
|
|
|
// synthetic move
|
2018-08-04 00:01:27 +00:00
|
|
|
if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0) {
|
|
|
|
if (lfs->globals.s.moveid == lfs_tagid(foundtag)) {
|
2018-08-01 15:24:59 +00:00
|
|
|
foundtag = LFS_ERR_NOENT;
|
|
|
|
} else if (lfs_tagisvalid(foundtag) &&
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs->globals.s.moveid < lfs_tagid(foundtag)) {
|
2018-08-01 15:24:59 +00:00
|
|
|
foundtag -= LFS_MKTAG(0, 1, 0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return foundtag;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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_fetch(lfs_t *lfs,
|
|
|
|
lfs_mdir_t *dir, const lfs_block_t pair[2]) {
|
|
|
|
int32_t res = lfs_dir_find(lfs, dir, pair, 0xffffffff, 0xffffffff, NULL);
|
|
|
|
if (res < 0 && res != LFS_ERR_NOENT) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int32_t lfs_dir_get(lfs_t *lfs, lfs_mdir_t *dir,
|
|
|
|
uint32_t getmask, uint32_t gettag, void *buffer) {
|
|
|
|
int32_t getdiff = 0;
|
2018-08-04 00:01:27 +00:00
|
|
|
if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0 &&
|
|
|
|
lfs_tagid(gettag) <= lfs->globals.s.moveid) {
|
2018-08-01 15:24:59 +00:00
|
|
|
// synthetic moves
|
|
|
|
getdiff = LFS_MKTAG(0, 1, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return lfs_commitget(lfs, dir->pair[0], dir->off, dir->etag,
|
|
|
|
getmask, gettag, getdiff, buffer, false);
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_dir_compact(lfs_t *lfs,
|
|
|
|
lfs_mdir_t *dir, const lfs_mattr_t *attrs,
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t *source, uint16_t begin, uint16_t end) {
|
2018-05-28 07:08:16 +00:00
|
|
|
// save some state in case block is bad
|
|
|
|
const lfs_block_t oldpair[2] = {dir->pair[1], dir->pair[0]};
|
|
|
|
bool relocated = false;
|
|
|
|
|
2018-07-04 06:35:04 +00:00
|
|
|
// There's nothing special about our global delta, so feed it back
|
|
|
|
// into the global global delta
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &dir->locals);
|
|
|
|
lfs_globalzero(&dir->locals);
|
2018-07-04 06:35:04 +00:00
|
|
|
|
2018-05-28 07:08:16 +00:00
|
|
|
// increment revision count
|
|
|
|
dir->rev += 1;
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
// last complete id
|
|
|
|
int16_t ack = -1;
|
|
|
|
dir->count = end - begin;
|
|
|
|
|
|
|
|
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),
|
|
|
|
.crc = crc,
|
|
|
|
.ptag = 0,
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// space is complicated, we need room for tail, crc, globals,
|
|
|
|
// and we cap at half a block to give room for metadata updates
|
|
|
|
.begin = 0,
|
|
|
|
.end = lfs_min(
|
|
|
|
lfs_alignup(lfs->cfg->block_size/2, lfs->cfg->prog_size),
|
|
|
|
lfs->cfg->block_size - 34),
|
2018-05-28 07:08:16 +00:00
|
|
|
};
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// commit with a move
|
|
|
|
for (uint16_t id = begin; id < end; id++) {
|
|
|
|
err = lfs_commitmove(lfs, &commit,
|
|
|
|
id, id - begin, source, attrs);
|
2018-05-28 07:08:16 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_NOSPC) {
|
|
|
|
goto split;
|
|
|
|
} else if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
2018-07-13 20:04:31 +00:00
|
|
|
|
|
|
|
ack = id;
|
2018-05-28 07:08:16 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// reopen reserved space at the end
|
|
|
|
commit.end = lfs->cfg->block_size - 8;
|
|
|
|
|
|
|
|
if (!relocated) {
|
|
|
|
err = lfs_commitglobals(lfs, &commit, &dir->locals);
|
2018-05-28 07:08:16 +00:00
|
|
|
if (err) {
|
2018-07-13 20:04:31 +00:00
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
2018-05-28 07:08:16 +00:00
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-02 03:29:42 +00:00
|
|
|
if (!lfs_pairisnull(dir->tail)) {
|
2018-07-09 16:47:04 +00:00
|
|
|
// commit tail, which may be new after last size check
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairtole32(dir->tail);
|
2018-07-13 20:04:31 +00:00
|
|
|
err = lfs_commitattr(lfs, &commit,
|
2018-07-13 00:07:56 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_TAIL + dir->split,
|
2018-07-13 01:43:55 +00:00
|
|
|
0x3ff, sizeof(dir->tail)), dir->tail);
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(dir->tail);
|
Introduced xored-globals logic to fix fundamental problem with moves
This was a big roadblock for a while: with the new feature of inlined
files, the existing move logic was fundamentally flawed.
To pull off atomic moves between two different metadata-pairs, littlefs
uses a simple, if a bit clumsy trick.
1. Marks entry as "moving"
2. Copies entry to new metadata-pair
3. Deletes old entry
If power is lost before the move operation is completed, we will find the
"moving" tag. This means there may or may not be an incomplete move on
the filesystem. In this case, we simply search for the moved entry, if
we find it, we remove the old entry, otherwise we just remove the
"moving" tag.
This worked perfectly, until we introduced inlined files. See, unlike
the existing directory and ctz entries, inlined files have no guarantee
they are unique. There is nothing we can search for that will allow us
to find a moved file unless we assign entries globally-unique ids. (note
that moves are fundamentally rename operations, so searching for names
does not make sense).
---
Solving this problem required completely restructuring how littlefs
handled moves and pulled out a really old idea that had been left in the
cutting room floor back when littlefs was going through many
designs: xored-globals.
The problem xored-globals solves is the need to maintain some global state
via commits to these distributed, independent metadata-pairs. The idea
is that we can use some sort of symmetric operation, such as xor, to
introduces deltas of the global state that can be committed atomically
along with any other info to these metadata-pairs.
This means that to figure out our global state, we xor together the global
delta stored in every metadata-pair.
Which means any commit can update the global state atomically, opening
up a whole new set atomic possibilities.
There is a couple of downsides. These globals may end up with deltas on
every single metadata-pair, effectively duplicating the data for each
block. Additionally, these globals need to have multiple copies in RAM.
This means and globals need to be a bounded size and very small, since even
small globals will have a large footprint.
---
On top of xored-globals, it's trivial to fix our move logic. Here we've
added an indirect delete tag which allows us to atomically specify a
delete of any entry on the filesystem.
Our move operation is now:
1. Copy entry to new metadata-pair and atomically xor globals to
indirectly delete our original entry.
2. Delete the original entry and xor globals to remove the indirect
delete.
Extra exciting is that this now takes our relatively clumsy move
operation into a sexy guaranteed O(1) move operation with no searching
necessary (though we do need to xor globals during mount).
Also reintroduced entry struct, now with a specific purpose to describe
the metadata-pair + id combo needed by indirect deletes to locate an
entry.
2018-05-29 17:35:23 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
err = lfs_commitcrc(lfs, &commit);
|
2018-05-28 07:08:16 +00:00
|
|
|
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,
|
|
|
|
// drop caches and create tail
|
|
|
|
lfs->pcache.block = 0xffffffff;
|
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
if (ack == -1) {
|
|
|
|
// If we can't fit in this block, we won't fit in next block
|
|
|
|
return LFS_ERR_NOSPC;
|
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t tail;
|
2018-08-01 15:24:59 +00:00
|
|
|
int err = lfs_dir_alloc(lfs, &tail);
|
2018-05-28 07:08:16 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
tail.split = dir->split;
|
|
|
|
tail.tail[0] = dir->tail[0];
|
|
|
|
tail.tail[1] = dir->tail[1];
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
err = lfs_dir_compact(lfs, &tail, attrs, dir, ack+1, end);
|
2018-05-28 07:08:16 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
end = ack+1;
|
|
|
|
dir->tail[0] = tail.pair[0];
|
|
|
|
dir->tail[1] = tail.pair[1];
|
|
|
|
dir->split = true;
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
if (!relocated) {
|
|
|
|
// successful commit, update globals
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&dir->locals, &lfs->locals);
|
|
|
|
lfs_globalzero(&lfs->locals);
|
2018-07-13 20:04:31 +00:00
|
|
|
} else {
|
2018-05-28 07:08:16 +00:00
|
|
|
// update references if we relocated
|
|
|
|
LFS_DEBUG("Relocating %d %d to %d %d",
|
|
|
|
oldpair[0], oldpair[1], dir->pair[0], dir->pair[1]);
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_relocate(lfs, oldpair, dir->pair);
|
2018-05-28 07:08:16 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
static int lfs_dir_commit(lfs_t *lfs, lfs_mdir_t *dir,
|
|
|
|
const lfs_mattr_t *attrs) {
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_mattr_t cancelattr;
|
|
|
|
lfs_global_t canceldiff;
|
|
|
|
lfs_globalzero(&canceldiff);
|
2018-08-04 00:01:27 +00:00
|
|
|
if (lfs_paircmp(dir->pair, lfs->globals.s.movepair) == 0) {
|
2018-07-17 23:31:30 +00:00
|
|
|
// Wait, we have the move? Just cancel this out here
|
|
|
|
// We need to, or else the move can become outdated
|
2018-08-04 00:01:27 +00:00
|
|
|
canceldiff.s.movepair[0] ^= lfs->globals.s.movepair[0] ^ 0xffffffff;
|
|
|
|
canceldiff.s.movepair[1] ^= lfs->globals.s.movepair[1] ^ 0xffffffff;
|
|
|
|
canceldiff.s.moveid ^= lfs->globals.s.moveid ^ 0x3ff;
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_globalfromle32(&lfs->locals);
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &canceldiff);
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_globaltole32(&lfs->locals);
|
2018-07-17 23:31:30 +00:00
|
|
|
|
2018-08-04 00:01:27 +00:00
|
|
|
cancelattr.tag = LFS_MKTAG(LFS_TYPE_DELETE, lfs->globals.s.moveid, 0);
|
2018-07-31 13:07:36 +00:00
|
|
|
cancelattr.next = attrs;
|
|
|
|
attrs = &cancelattr;
|
2018-07-17 23:31:30 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// calculate new directory size
|
|
|
|
uint32_t deletetag = 0xffffffff;
|
|
|
|
for (const lfs_mattr_t *a = attrs; a; a = a->next) {
|
|
|
|
if (lfs_tagid(a->tag) < 0x3ff && lfs_tagid(a->tag) >= dir->count) {
|
|
|
|
dir->count = lfs_tagid(a->tag)+1;
|
2018-05-29 05:50:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
if (lfs_tagtype(a->tag) == LFS_TYPE_DELETE) {
|
|
|
|
LFS_ASSERT(dir->count > 0);
|
|
|
|
dir->count -= 1;
|
|
|
|
deletetag = a->tag;
|
|
|
|
|
|
|
|
if (dir->count == 0) {
|
|
|
|
// should we actually drop the directory block?
|
|
|
|
lfs_mdir_t pdir;
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_pred(lfs, dir->pair, &pdir);
|
2018-07-17 23:31:30 +00:00
|
|
|
if (err && err != LFS_ERR_NOENT) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (err != LFS_ERR_NOENT && pdir.split) {
|
|
|
|
// steal tail and global state
|
|
|
|
pdir.split = dir->split;
|
|
|
|
pdir.tail[0] = dir->tail[0];
|
|
|
|
pdir.tail[1] = dir->tail[1];
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &dir->locals);
|
2018-07-17 23:31:30 +00:00
|
|
|
return lfs_dir_commit(lfs, &pdir,
|
|
|
|
LFS_MKATTR(LFS_TYPE_TAIL + pdir.split, 0x3ff,
|
|
|
|
pdir.tail, sizeof(pdir.tail),
|
|
|
|
NULL));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!dir->erased) {
|
|
|
|
compact:
|
|
|
|
// fall back to compaction
|
|
|
|
lfs->pcache.block = 0xffffffff;
|
|
|
|
int err = lfs_dir_compact(lfs, dir, attrs, dir, 0, dir->count);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// try to commit
|
2018-05-29 05:50:47 +00:00
|
|
|
struct lfs_commit commit = {
|
|
|
|
.block = dir->pair[0],
|
|
|
|
.off = dir->off,
|
|
|
|
.crc = 0xffffffff,
|
|
|
|
.ptag = dir->etag,
|
2018-07-13 20:04:31 +00:00
|
|
|
|
|
|
|
.begin = dir->off,
|
|
|
|
.end = lfs->cfg->block_size - 8,
|
2018-05-29 05:50:47 +00:00
|
|
|
};
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
for (const lfs_mattr_t *a = attrs; a; a = a->next) {
|
2018-07-17 23:31:30 +00:00
|
|
|
if (lfs_tagtype(a->tag) != LFS_TYPE_DELETE) {
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairtole32(dir->tail);
|
2018-07-17 23:31:30 +00:00
|
|
|
int err = lfs_commitattr(lfs, &commit, a->tag, a->buffer);
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(dir->tail);
|
2018-07-17 23:31:30 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
|
|
|
goto compact;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lfs_tagisvalid(deletetag)) {
|
|
|
|
// special case for deletes, since order matters
|
|
|
|
int err = lfs_commitattr(lfs, &commit, deletetag, NULL);
|
2018-07-12 23:11:18 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
|
|
|
goto compact;
|
|
|
|
}
|
|
|
|
return err;
|
2018-05-29 05:50:47 +00:00
|
|
|
}
|
|
|
|
}
|
2018-05-28 07:08:16 +00:00
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_commitglobals(lfs, &commit, &dir->locals);
|
2018-07-04 06:35:04 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
|
|
|
goto compact;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
2018-07-04 06:35:04 +00:00
|
|
|
return err;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
err = lfs_commitcrc(lfs, &commit);
|
2018-05-29 05:50:47 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_NOSPC || err == LFS_ERR_CORRUPT) {
|
|
|
|
goto compact;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-28 07:08:16 +00:00
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// successful commit, update dir
|
2018-05-29 05:50:47 +00:00
|
|
|
dir->off = commit.off;
|
|
|
|
dir->etag = commit.ptag;
|
2018-07-13 20:04:31 +00:00
|
|
|
// successful commit, update globals
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&dir->locals, &lfs->locals);
|
|
|
|
lfs_globalzero(&lfs->locals);
|
2018-07-17 23:31:30 +00:00
|
|
|
}
|
2018-05-29 05:50:47 +00:00
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
// update globals that are affected
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->globals, &canceldiff);
|
2018-05-19 23:25:47 +00:00
|
|
|
|
2018-05-29 05:50:47 +00:00
|
|
|
// update any directories that are affected
|
2018-08-01 15:24:59 +00:00
|
|
|
for (lfs_mlist_t *d = lfs->mlist; d; d = d->next) {
|
2018-05-29 06:11:26 +00:00
|
|
|
if (lfs_paircmp(d->m.pair, dir->pair) == 0) {
|
|
|
|
d->m = *dir;
|
2018-08-01 15:24:59 +00:00
|
|
|
if (d->id == lfs_tagid(deletetag)) {
|
|
|
|
d->m.pair[0] = 0xffffffff;
|
|
|
|
d->m.pair[1] = 0xffffffff;
|
|
|
|
} else if (d->id > lfs_tagid(deletetag)) {
|
|
|
|
d->id -= 1;
|
|
|
|
if (d->type == LFS_TYPE_DIR) {
|
|
|
|
((lfs_dir_t*)d)->pos -= 1;
|
|
|
|
}
|
2018-07-13 20:04:31 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
while (d->id >= d->m.count && d->m.split) {
|
|
|
|
// we split and id is on tail now
|
|
|
|
d->id -= d->m.count;
|
|
|
|
int err = lfs_dir_fetch(lfs, &d->m, d->m.tail);
|
2018-07-13 20:04:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
static int32_t lfs_dir_lookup(lfs_t *lfs, lfs_mdir_t *dir, const char **path) {
|
2018-07-28 17:07:18 +00:00
|
|
|
// we reduce path to a single name if we can find it
|
2018-07-09 22:29:40 +00:00
|
|
|
const char *name = *path;
|
2018-07-28 17:07:18 +00:00
|
|
|
*path = NULL;
|
|
|
|
|
|
|
|
// default to root dir
|
|
|
|
int32_t tag = LFS_MKTAG(LFS_TYPE_DIR, 0x3ff, 0);
|
|
|
|
lfs_block_t pair[2] = {lfs->root[0], lfs->root[1]};
|
2018-05-19 23:25:47 +00:00
|
|
|
|
|
|
|
while (true) {
|
2018-07-28 17:07:18 +00:00
|
|
|
nextname:
|
2018-05-19 23:25:47 +00:00
|
|
|
// skip slashes
|
2018-07-09 22:29:40 +00:00
|
|
|
name += strspn(name, "/");
|
2018-07-28 17:07:18 +00:00
|
|
|
lfs_size_t namelen = strcspn(name, "/");
|
2018-05-19 23:25:47 +00:00
|
|
|
|
|
|
|
// skip '.' and root '..'
|
2018-07-09 22:29:40 +00:00
|
|
|
if ((namelen == 1 && memcmp(name, ".", 1) == 0) ||
|
|
|
|
(namelen == 2 && memcmp(name, "..", 2) == 0)) {
|
|
|
|
name += namelen;
|
2018-05-19 23:25:47 +00:00
|
|
|
goto nextname;
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip if matched by '..' in name
|
2018-07-09 22:29:40 +00:00
|
|
|
const char *suffix = name + namelen;
|
2018-05-19 23:25:47 +00:00
|
|
|
lfs_size_t sufflen;
|
|
|
|
int 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) {
|
2018-07-09 22:29:40 +00:00
|
|
|
name = suffix + sufflen;
|
2018-05-19 23:25:47 +00:00
|
|
|
goto nextname;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
depth += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
suffix += sufflen;
|
|
|
|
}
|
|
|
|
|
2018-07-28 17:07:18 +00:00
|
|
|
// found path
|
|
|
|
if (name[0] == '\0') {
|
|
|
|
return tag;
|
|
|
|
}
|
2018-05-19 23:25:47 +00:00
|
|
|
|
2018-07-28 17:07:18 +00:00
|
|
|
// update what we've found if path is only a name
|
|
|
|
if (strchr(name, '/') == NULL) {
|
|
|
|
*path = name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// only continue if we hit a directory
|
|
|
|
if (lfs_tagtype(tag) != LFS_TYPE_DIR) {
|
|
|
|
return LFS_ERR_NOTDIR;
|
|
|
|
}
|
|
|
|
|
|
|
|
// grab the entry data
|
|
|
|
if (lfs_tagid(tag) != 0x3ff) {
|
|
|
|
int32_t res = lfs_dir_get(lfs, dir, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(pair);
|
2018-07-28 17:07:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// find entry matching name
|
2018-05-19 23:25:47 +00:00
|
|
|
while (true) {
|
2018-07-13 01:43:55 +00:00
|
|
|
tag = lfs_dir_find(lfs, dir, pair, 0x7c000fff,
|
|
|
|
LFS_MKTAG(LFS_TYPE_NAME, 0, namelen), name);
|
2018-07-13 01:22:06 +00:00
|
|
|
if (tag < 0 && tag != LFS_ERR_NOENT) {
|
|
|
|
return tag;
|
2018-05-19 23:25:47 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
if (tag != LFS_ERR_NOENT) {
|
2018-05-19 23:25:47 +00:00
|
|
|
// found it
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2018-05-27 15:15:28 +00:00
|
|
|
if (!dir->split) {
|
2018-07-28 17:07:18 +00:00
|
|
|
// couldn't find it
|
2018-05-19 23:25:47 +00:00
|
|
|
return LFS_ERR_NOENT;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
pair[0] = dir->tail[0];
|
|
|
|
pair[1] = dir->tail[1];
|
2018-05-19 23:25:47 +00:00
|
|
|
}
|
2018-05-27 15:15:28 +00:00
|
|
|
|
2018-07-28 17:07:18 +00:00
|
|
|
// to next name
|
2018-07-09 22:29:40 +00:00
|
|
|
name += namelen;
|
2018-03-11 16:28:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_dir_getinfo(lfs_t *lfs, lfs_mdir_t *dir,
|
2018-08-01 10:52:48 +00:00
|
|
|
uint16_t id, struct lfs_info *info) {
|
|
|
|
if (id == 0x3ff) {
|
|
|
|
// special case for root
|
|
|
|
strcpy(info->name, "/");
|
|
|
|
info->type = LFS_TYPE_DIR;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
int32_t tag = lfs_dir_get(lfs, dir, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_NAME, id, lfs->name_size+1), info->name);
|
|
|
|
if (tag < 0) {
|
|
|
|
return tag;
|
|
|
|
}
|
|
|
|
|
|
|
|
info->type = lfs_tagtype(tag);
|
|
|
|
|
|
|
|
struct lfs_ctz ctz;
|
|
|
|
tag = lfs_dir_get(lfs, dir, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
|
|
|
|
if (tag < 0) {
|
|
|
|
return tag;
|
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_ctzfromle32(&ctz);
|
2018-07-13 20:04:31 +00:00
|
|
|
|
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_CTZSTRUCT) {
|
|
|
|
info->size = ctz.size;
|
|
|
|
} else if (lfs_tagtype(tag) == LFS_TYPE_INLINESTRUCT) {
|
|
|
|
info->size = lfs_tagsize(tag);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
/// Top level directory operations ///
|
2018-05-21 05:56:20 +00:00
|
|
|
int lfs_mkdir(lfs_t *lfs, const char *path) {
|
|
|
|
// deorphan if we haven't yet, needed at most once after poweron
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_forceconsistency(lfs);
|
2018-07-31 13:07:36 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t cwd;
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
|
2018-07-29 20:03:23 +00:00
|
|
|
if (!(res == LFS_ERR_NOENT && path)) {
|
2018-07-28 17:07:18 +00:00
|
|
|
return (res < 0) ? res : LFS_ERR_EXIST;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check that name fits
|
|
|
|
lfs_size_t nlen = strlen(path);
|
|
|
|
if (nlen > lfs->name_size) {
|
|
|
|
return LFS_ERR_NAMETOOLONG;
|
|
|
|
}
|
|
|
|
|
|
|
|
// build up new directory
|
|
|
|
lfs_alloc_ack(lfs);
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t dir;
|
2018-08-01 15:24:59 +00:00
|
|
|
err = lfs_dir_alloc(lfs, &dir);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
dir.tail[0] = cwd.tail[0];
|
|
|
|
dir.tail[1] = cwd.tail[1];
|
2018-05-29 00:49:20 +00:00
|
|
|
err = lfs_dir_commit(lfs, &dir, NULL);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get next slot and commit
|
2018-07-17 23:31:30 +00:00
|
|
|
uint16_t id = cwd.count;
|
2018-05-27 15:15:28 +00:00
|
|
|
cwd.tail[0] = dir.pair[0];
|
|
|
|
cwd.tail[1] = dir.pair[1];
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairtole32(dir.pair);
|
2018-07-13 00:07:56 +00:00
|
|
|
err = lfs_dir_commit(lfs, &cwd,
|
|
|
|
LFS_MKATTR(LFS_TYPE_DIR, id, path, nlen,
|
|
|
|
LFS_MKATTR(LFS_TYPE_DIRSTRUCT, id, dir.pair, sizeof(dir.pair),
|
|
|
|
LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff, cwd.tail, sizeof(cwd.tail),
|
2018-07-13 01:43:55 +00:00
|
|
|
NULL))));
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(dir.pair);
|
2018-07-13 00:07:56 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path) {
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t tag = lfs_dir_lookup(lfs, &dir->m, &path);
|
|
|
|
if (tag < 0) {
|
|
|
|
return tag;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 00:07:56 +00:00
|
|
|
if (lfs_tagtype(tag) != LFS_TYPE_DIR) {
|
2018-07-09 17:51:31 +00:00
|
|
|
return LFS_ERR_NOTDIR;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_block_t pair[2];
|
2018-07-13 01:22:06 +00:00
|
|
|
if (lfs_tagid(tag) == 0x3ff) {
|
2018-05-26 18:50:06 +00:00
|
|
|
// handle root dir separately
|
2018-07-13 01:43:55 +00:00
|
|
|
pair[0] = lfs->root[0];
|
|
|
|
pair[1] = lfs->root[1];
|
2018-05-26 18:50:06 +00:00
|
|
|
} else {
|
|
|
|
// get dir pair from parent
|
2018-07-13 01:43:55 +00:00
|
|
|
int32_t res = lfs_dir_get(lfs, &dir->m, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
2018-05-26 18:50:06 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(pair);
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
// fetch first pair
|
2018-07-13 01:43:55 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir->m, pair);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-05-29 05:50:47 +00:00
|
|
|
// setup entry
|
2018-05-29 06:11:26 +00:00
|
|
|
dir->head[0] = dir->m.pair[0];
|
|
|
|
dir->head[1] = dir->m.pair[1];
|
2018-05-21 05:56:20 +00:00
|
|
|
dir->id = 0;
|
2018-05-29 05:50:47 +00:00
|
|
|
dir->pos = 0;
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
// add to list of mdirs
|
|
|
|
dir->type = LFS_TYPE_DIR;
|
|
|
|
dir->next = (lfs_dir_t*)lfs->mlist;
|
|
|
|
lfs->mlist = (lfs_mlist_t*)dir;
|
2018-05-21 05:56:20 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir) {
|
2018-08-01 15:24:59 +00:00
|
|
|
// remove from list of mdirs
|
|
|
|
for (lfs_mlist_t **p = &lfs->mlist; *p; p = &(*p)->next) {
|
|
|
|
if (*p == (lfs_mlist_t*)dir) {
|
|
|
|
*p = (*p)->next;
|
2018-05-21 05:56:20 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info) {
|
2018-05-21 05:56:20 +00:00
|
|
|
memset(info, 0, sizeof(*info));
|
|
|
|
|
|
|
|
// special offset for '.' and '..'
|
|
|
|
if (dir->pos == 0) {
|
|
|
|
info->type = LFS_TYPE_DIR;
|
|
|
|
strcpy(info->name, ".");
|
|
|
|
dir->pos += 1;
|
|
|
|
return 1;
|
|
|
|
} else if (dir->pos == 1) {
|
|
|
|
info->type = LFS_TYPE_DIR;
|
|
|
|
strcpy(info->name, "..");
|
|
|
|
dir->pos += 1;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (true) {
|
2018-05-29 06:11:26 +00:00
|
|
|
if (dir->id == dir->m.count) {
|
|
|
|
if (!dir->m.split) {
|
2018-05-21 05:56:20 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
dir->id = 0;
|
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
int err = lfs_dir_getinfo(lfs, &dir->m, dir->id, info);
|
2018-05-26 18:50:06 +00:00
|
|
|
if (err && err != LFS_ERR_NOENT) {
|
2018-05-21 05:56:20 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
dir->id += 1;
|
2018-05-26 18:50:06 +00:00
|
|
|
if (err != LFS_ERR_NOENT) {
|
|
|
|
break;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
dir->pos += 1;
|
|
|
|
return true;
|
|
|
|
}
|
2017-03-13 00:41:08 +00:00
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off) {
|
2018-05-26 00:04:01 +00:00
|
|
|
// simply walk from head dir
|
2018-05-26 18:50:06 +00:00
|
|
|
int err = lfs_dir_rewind(lfs, dir);
|
2017-03-25 21:20:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 00:04:01 +00:00
|
|
|
// first two for ./..
|
2018-05-28 14:17:44 +00:00
|
|
|
dir->pos = lfs_min(2, off);
|
|
|
|
off -= dir->pos;
|
2017-03-25 21:20:31 +00:00
|
|
|
|
2018-05-26 00:04:01 +00:00
|
|
|
while (off != 0) {
|
2018-05-29 06:11:26 +00:00
|
|
|
dir->id = lfs_min(dir->m.count, off);
|
2018-05-28 14:17:44 +00:00
|
|
|
dir->pos += dir->id;
|
|
|
|
off -= dir->id;
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
if (dir->id == dir->m.count) {
|
|
|
|
if (!dir->m.split) {
|
2018-05-26 00:04:01 +00:00
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
2017-04-15 16:26:37 +00:00
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir->m, dir->m.tail);
|
2018-05-26 00:04:01 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir) {
|
2018-02-04 19:10:07 +00:00
|
|
|
(void)lfs;
|
2017-04-23 04:11:13 +00:00
|
|
|
return dir->pos;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir) {
|
2017-04-23 04:11:13 +00:00
|
|
|
// reload the head dir
|
2018-05-29 06:11:26 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir->m, dir->head);
|
2017-04-23 04:11:13 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
dir->m.pair[0] = dir->head[0];
|
|
|
|
dir->m.pair[1] = dir->head[1];
|
2018-05-26 00:04:01 +00:00
|
|
|
dir->id = 0;
|
2018-05-29 05:50:47 +00:00
|
|
|
dir->pos = 0;
|
2017-04-23 04:11:13 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-03-25 21:20:31 +00:00
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
/// File index list operations ///
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_ctzindex(lfs_t *lfs, lfs_off_t *off) {
|
2017-10-17 00:08:47 +00:00
|
|
|
lfs_off_t size = *off;
|
2017-10-18 05:33:59 +00:00
|
|
|
lfs_off_t b = lfs->cfg->block_size - 2*4;
|
|
|
|
lfs_off_t i = size / b;
|
2017-10-17 00:08:47 +00:00
|
|
|
if (i == 0) {
|
|
|
|
return 0;
|
2017-04-23 00:48:31 +00:00
|
|
|
}
|
|
|
|
|
2017-10-18 05:33:59 +00:00
|
|
|
i = (size - 4*(lfs_popc(i-1)+2)) / b;
|
|
|
|
*off = size - b*i - 4*lfs_popc(i);
|
|
|
|
return i;
|
2017-04-23 00:48:31 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_ctzfind(lfs_t *lfs,
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_cache_t *rcache, const lfs_cache_t *pcache,
|
|
|
|
lfs_block_t head, lfs_size_t size,
|
2017-04-23 00:48:31 +00:00
|
|
|
lfs_size_t pos, lfs_block_t *block, lfs_off_t *off) {
|
|
|
|
if (size == 0) {
|
2017-10-17 00:31:56 +00:00
|
|
|
*block = 0xffffffff;
|
2017-04-23 00:48:31 +00:00
|
|
|
*off = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
lfs_off_t current = lfs_ctzindex(lfs, &(lfs_off_t){size-1});
|
|
|
|
lfs_off_t target = lfs_ctzindex(lfs, &pos);
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2017-04-23 02:42:22 +00:00
|
|
|
while (current > target) {
|
2017-04-23 00:48:31 +00:00
|
|
|
lfs_size_t skip = lfs_min(
|
|
|
|
lfs_npw2(current-target+1) - 1,
|
2017-10-10 23:48:24 +00:00
|
|
|
lfs_ctz(current));
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_read(lfs, pcache, rcache, false,
|
|
|
|
head, 4*skip, &head, 4);
|
2018-02-02 11:58:43 +00:00
|
|
|
head = lfs_fromle32(head);
|
2017-04-23 00:48:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-01-29 21:20:12 +00:00
|
|
|
LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
|
2017-04-23 00:48:31 +00:00
|
|
|
current -= 1 << skip;
|
|
|
|
}
|
|
|
|
|
|
|
|
*block = head;
|
|
|
|
*off = pos;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_ctzextend(lfs_t *lfs,
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_cache_t *rcache, lfs_cache_t *pcache,
|
2017-04-23 00:48:31 +00:00
|
|
|
lfs_block_t head, lfs_size_t size,
|
2017-11-16 21:10:17 +00:00
|
|
|
lfs_block_t *block, lfs_off_t *off) {
|
2017-05-14 17:01:45 +00:00
|
|
|
while (true) {
|
2017-11-16 21:10:17 +00:00
|
|
|
// go ahead and grab a block
|
|
|
|
lfs_block_t nblock;
|
|
|
|
int err = lfs_alloc(lfs, &nblock);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-01-29 21:20:12 +00:00
|
|
|
LFS_ASSERT(nblock >= 2 && nblock <= lfs->cfg->block_count);
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2017-11-16 21:10:17 +00:00
|
|
|
if (true) {
|
|
|
|
err = lfs_bd_erase(lfs, nblock);
|
2017-10-17 00:31:56 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2017-10-17 00:31:56 +00:00
|
|
|
if (size == 0) {
|
2017-11-16 21:10:17 +00:00
|
|
|
*block = nblock;
|
2017-10-17 00:31:56 +00:00
|
|
|
*off = 0;
|
|
|
|
return 0;
|
|
|
|
}
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2017-10-17 00:31:56 +00:00
|
|
|
size -= 1;
|
2018-07-13 20:04:31 +00:00
|
|
|
lfs_off_t index = lfs_ctzindex(lfs, &size);
|
2017-10-17 00:31:56 +00:00
|
|
|
size += 1;
|
|
|
|
|
|
|
|
// just copy out the last block if it is incomplete
|
|
|
|
if (size != lfs->cfg->block_size) {
|
|
|
|
for (lfs_off_t i = 0; i < size; i++) {
|
|
|
|
uint8_t data;
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_read(lfs, NULL, rcache, true,
|
2017-10-17 00:31:56 +00:00
|
|
|
head, i, &data, 1);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-04-23 02:42:22 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_prog(lfs, pcache, rcache, true,
|
2017-11-16 21:10:17 +00:00
|
|
|
nblock, i, &data, 1);
|
2017-10-17 00:31:56 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
|
|
|
|
2017-11-16 21:10:17 +00:00
|
|
|
*block = nblock;
|
2017-10-17 00:31:56 +00:00
|
|
|
*off = size;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// append block
|
|
|
|
index += 1;
|
|
|
|
lfs_size_t skips = lfs_ctz(index) + 1;
|
|
|
|
|
|
|
|
for (lfs_off_t i = 0; i < skips; i++) {
|
2018-02-02 11:58:43 +00:00
|
|
|
head = lfs_tole32(head);
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_prog(lfs, pcache, rcache, true,
|
2017-11-16 21:10:17 +00:00
|
|
|
nblock, 4*i, &head, 4);
|
2018-02-02 11:58:43 +00:00
|
|
|
head = lfs_fromle32(head);
|
2017-05-14 17:01:45 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-10-17 00:31:56 +00:00
|
|
|
if (i != skips-1) {
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_read(lfs, NULL, rcache, false,
|
2017-10-17 00:31:56 +00:00
|
|
|
head, 4*i, &head, 4);
|
2018-02-02 11:58:43 +00:00
|
|
|
head = lfs_fromle32(head);
|
2017-10-17 00:31:56 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
|
|
|
|
2018-01-29 21:20:12 +00:00
|
|
|
LFS_ASSERT(head >= 2 && head <= lfs->cfg->block_count);
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
2017-09-17 17:53:18 +00:00
|
|
|
|
2017-11-16 21:10:17 +00:00
|
|
|
*block = nblock;
|
2017-10-17 00:31:56 +00:00
|
|
|
*off = 4*skips;
|
|
|
|
return 0;
|
2017-04-23 02:42:22 +00:00
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
relocate:
|
2017-11-16 21:10:17 +00:00
|
|
|
LFS_DEBUG("Bad block at %d", nblock);
|
2017-04-23 00:48:31 +00:00
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// just clear cache and try a new block
|
|
|
|
pcache->block = 0xffffffff;
|
2017-04-23 00:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
static int lfs_ctztraverse(lfs_t *lfs,
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_cache_t *rcache, const lfs_cache_t *pcache,
|
2017-04-23 00:48:31 +00:00
|
|
|
lfs_block_t head, lfs_size_t size,
|
2018-07-30 19:40:27 +00:00
|
|
|
int (*cb)(void*, lfs_block_t), void *data) {
|
2017-04-23 00:48:31 +00:00
|
|
|
if (size == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
lfs_off_t index = lfs_ctzindex(lfs, &(lfs_off_t){size-1});
|
2017-04-23 00:48:31 +00:00
|
|
|
|
|
|
|
while (true) {
|
2018-07-30 19:40:27 +00:00
|
|
|
int err = cb(data, head);
|
2017-04-23 00:48:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (index == 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-12-27 18:30:01 +00:00
|
|
|
lfs_block_t heads[2];
|
|
|
|
int count = 2 - (index & 1);
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_read(lfs, pcache, rcache, false,
|
|
|
|
head, 0, &heads, count*4);
|
2018-02-02 11:58:43 +00:00
|
|
|
heads[0] = lfs_fromle32(heads[0]);
|
|
|
|
heads[1] = lfs_fromle32(heads[1]);
|
2017-04-23 00:48:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-12-27 18:30:01 +00:00
|
|
|
for (int i = 0; i < count-1; i++) {
|
2018-07-30 19:40:27 +00:00
|
|
|
err = cb(data, heads[i]);
|
2017-12-27 18:30:01 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
head = heads[count-1];
|
|
|
|
index -= count;
|
2017-04-23 00:48:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
/// Top level file operations ///
|
2018-07-29 20:03:23 +00:00
|
|
|
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
|
|
|
|
const char *path, int flags,
|
|
|
|
const struct lfs_file_config *cfg) {
|
2018-05-22 22:43:39 +00:00
|
|
|
// deorphan if we haven't yet, needed at most once after poweron
|
2018-07-31 13:07:36 +00:00
|
|
|
if ((flags & 3) != LFS_O_RDONLY) {
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_forceconsistency(lfs);
|
2018-05-22 22:43:39 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
// setup simple file details
|
|
|
|
int err = 0;
|
|
|
|
file->cfg = cfg;
|
|
|
|
file->flags = flags;
|
|
|
|
file->pos = 0;
|
|
|
|
file->cache.buffer = NULL;
|
|
|
|
|
2018-05-22 22:43:39 +00:00
|
|
|
// allocate entry for file if it doesn't exist
|
2018-08-01 15:24:59 +00:00
|
|
|
int32_t tag = lfs_dir_lookup(lfs, &file->m, &path);
|
2018-07-29 20:03:23 +00:00
|
|
|
if (tag < 0 && !(tag == LFS_ERR_NOENT && path)) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = tag;
|
|
|
|
goto cleanup;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
// get id, add to list of mdirs to catch update changes
|
|
|
|
file->id = lfs_tagid(tag);
|
|
|
|
file->type = LFS_TYPE_REG;
|
|
|
|
file->next = (lfs_file_t*)lfs->mlist;
|
|
|
|
lfs->mlist = (lfs_mlist_t*)file;
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
if (tag == LFS_ERR_NOENT) {
|
2018-05-22 22:43:39 +00:00
|
|
|
if (!(flags & LFS_O_CREAT)) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_NOENT;
|
|
|
|
goto cleanup;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// check that name fits
|
|
|
|
lfs_size_t nlen = strlen(path);
|
|
|
|
if (nlen > lfs->name_size) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_NAMETOOLONG;
|
|
|
|
goto cleanup;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// get next slot and create entry to remember name
|
2018-08-01 15:24:59 +00:00
|
|
|
file->id = file->m.count;
|
|
|
|
int err = lfs_dir_commit(lfs, &file->m,
|
|
|
|
LFS_MKATTR(LFS_TYPE_REG, file->id, path, nlen,
|
|
|
|
LFS_MKATTR(LFS_TYPE_INLINESTRUCT, file->id, NULL, 0,
|
2018-07-13 01:43:55 +00:00
|
|
|
NULL)));
|
2018-05-26 18:50:06 +00:00
|
|
|
if (err) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_NAMETOOLONG;
|
|
|
|
goto cleanup;
|
2018-05-28 14:17:44 +00:00
|
|
|
}
|
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, 0, 0);
|
2018-07-13 01:43:55 +00:00
|
|
|
} else if (flags & LFS_O_EXCL) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_EXIST;
|
|
|
|
goto cleanup;
|
2018-07-13 01:43:55 +00:00
|
|
|
} else if (lfs_tagtype(tag) != LFS_TYPE_REG) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_ISDIR;
|
|
|
|
goto cleanup;
|
2018-07-13 01:43:55 +00:00
|
|
|
} else if (flags & LFS_O_TRUNC) {
|
|
|
|
// truncate if requested
|
2018-08-01 15:24:59 +00:00
|
|
|
tag = LFS_MKTAG(LFS_TYPE_INLINESTRUCT, file->id, 0);
|
|
|
|
file->flags |= LFS_F_DIRTY;
|
2018-05-26 18:50:06 +00:00
|
|
|
} else {
|
2018-07-13 01:43:55 +00:00
|
|
|
// try to load what's on disk, if it's inlined we'll fix it later
|
2018-08-01 15:24:59 +00:00
|
|
|
tag = lfs_dir_get(lfs, &file->m, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, file->id, 8), &file->ctz);
|
2018-07-13 01:43:55 +00:00
|
|
|
if (tag < 0) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = tag;
|
|
|
|
goto cleanup;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_ctzfromle32(&file->ctz);
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
2018-07-30 20:15:05 +00:00
|
|
|
// fetch attrs
|
|
|
|
for (const struct lfs_attr *a = file->cfg->attrs; a; a = a->next) {
|
|
|
|
if ((file->flags & 3) != LFS_O_WRONLY) {
|
2018-08-01 15:24:59 +00:00
|
|
|
int32_t res = lfs_dir_get(lfs, &file->m, 0x7ffff000,
|
2018-07-30 20:15:05 +00:00
|
|
|
LFS_MKTAG(0x100 | a->type, file->id, a->size), a->buffer);
|
|
|
|
if (res < 0 && res != LFS_ERR_NOENT) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = res;
|
|
|
|
goto cleanup;
|
2018-07-29 20:03:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-30 20:15:05 +00:00
|
|
|
if ((file->flags & 3) != LFS_O_RDONLY) {
|
|
|
|
if (a->size > lfs->attr_size) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_NOSPC;
|
|
|
|
goto cleanup;
|
2018-07-30 20:15:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
file->flags |= LFS_F_DIRTY;
|
|
|
|
}
|
2018-07-29 20:03:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
// allocate buffer if needed
|
2017-04-30 16:54:27 +00:00
|
|
|
file->cache.block = 0xffffffff;
|
2018-07-30 20:15:05 +00:00
|
|
|
if (file->cfg->buffer) {
|
2018-07-29 20:03:23 +00:00
|
|
|
file->cache.buffer = file->cfg->buffer;
|
2017-04-30 16:19:37 +00:00
|
|
|
} else {
|
2018-08-04 19:48:27 +00:00
|
|
|
file->cache.buffer = lfs_malloc(lfs->cfg->cache_size);
|
2017-04-30 16:19:37 +00:00
|
|
|
if (!file->cache.buffer) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = LFS_ERR_NOMEM;
|
|
|
|
goto cleanup;
|
2017-04-30 16:19:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_INLINESTRUCT) {
|
2018-04-03 13:28:09 +00:00
|
|
|
// load inline files
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.head = 0xfffffffe;
|
|
|
|
file->ctz.size = lfs_tagsize(tag);
|
2018-03-17 15:28:14 +00:00
|
|
|
file->flags |= LFS_F_INLINE;
|
2018-07-13 01:43:55 +00:00
|
|
|
file->cache.block = file->ctz.head;
|
2018-03-17 15:28:14 +00:00
|
|
|
file->cache.off = 0;
|
2018-08-04 19:48:27 +00:00
|
|
|
file->cache.size = lfs->cfg->cache_size;
|
2018-07-13 01:43:55 +00:00
|
|
|
|
|
|
|
// don't always read (may be new/trunc file)
|
|
|
|
if (file->ctz.size > 0) {
|
2018-08-01 15:24:59 +00:00
|
|
|
int32_t res = lfs_dir_get(lfs, &file->m, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, file->id, file->ctz.size),
|
2018-07-13 01:43:55 +00:00
|
|
|
file->cache.buffer);
|
|
|
|
if (res < 0) {
|
2018-08-01 15:24:59 +00:00
|
|
|
err = res;
|
|
|
|
goto cleanup;
|
2018-05-26 18:50:06 +00:00
|
|
|
}
|
2018-03-17 15:28:14 +00:00
|
|
|
}
|
2018-04-03 13:28:09 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
return 0;
|
2018-08-01 15:24:59 +00:00
|
|
|
|
|
|
|
cleanup:
|
|
|
|
// clean up lingering resources
|
|
|
|
file->flags |= LFS_F_ERRED;
|
|
|
|
lfs_file_close(lfs, file);
|
|
|
|
return err;
|
2017-03-20 03:00:56 +00:00
|
|
|
}
|
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
|
|
|
|
const char *path, int flags) {
|
2018-07-30 20:15:05 +00:00
|
|
|
static const struct lfs_file_config defaults = {0};
|
|
|
|
return lfs_file_opencfg(lfs, file, path, flags, &defaults);
|
2018-07-29 20:03:23 +00:00
|
|
|
}
|
|
|
|
|
2018-05-23 04:57:19 +00:00
|
|
|
int lfs_file_close(lfs_t *lfs, lfs_file_t *file) {
|
|
|
|
int err = lfs_file_sync(lfs, file);
|
2018-05-22 22:43:39 +00:00
|
|
|
|
2018-08-01 15:24:59 +00:00
|
|
|
// remove from list of mdirs
|
|
|
|
for (lfs_mlist_t **p = &lfs->mlist; *p; p = &(*p)->next) {
|
|
|
|
if (*p == (lfs_mlist_t*)file) {
|
|
|
|
*p = (*p)->next;
|
2018-05-23 04:57:19 +00:00
|
|
|
break;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-23 04:57:19 +00:00
|
|
|
// clean up memory
|
2018-07-30 20:15:05 +00:00
|
|
|
if (file->cfg->buffer) {
|
2018-05-23 04:57:19 +00:00
|
|
|
lfs_free(file->cache.buffer);
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
2018-05-23 04:57:19 +00:00
|
|
|
return err;
|
2018-05-22 22:43:39 +00:00
|
|
|
}
|
|
|
|
|
2018-04-11 00:55:17 +00:00
|
|
|
static int lfs_file_relocate(lfs_t *lfs, lfs_file_t *file) {
|
2018-07-31 02:12:00 +00:00
|
|
|
while (true) {
|
|
|
|
// just relocate what exists into new block
|
|
|
|
lfs_block_t nblock;
|
|
|
|
int err = lfs_alloc(lfs, &nblock);
|
2017-06-25 22:23:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-06-25 19:01:33 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
err = lfs_bd_erase(lfs, nblock);
|
2017-06-25 19:01:33 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
// either read from dirty cache or disk
|
|
|
|
for (lfs_off_t i = 0; i < file->off; i++) {
|
|
|
|
uint8_t data;
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_read(lfs, &file->cache, &lfs->rcache, true,
|
2018-07-31 02:12:00 +00:00
|
|
|
file->block, i, &data, 1);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-06-25 19:01:33 +00:00
|
|
|
|
2018-08-04 19:48:27 +00:00
|
|
|
err = lfs_cache_prog(lfs, &lfs->pcache, &lfs->rcache, true,
|
2018-07-31 02:12:00 +00:00
|
|
|
nblock, i, &data, 1);
|
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// copy over new state of file
|
2018-08-04 19:48:27 +00:00
|
|
|
memcpy(file->cache.buffer, lfs->pcache.buffer, lfs->cfg->cache_size);
|
2018-07-31 02:12:00 +00:00
|
|
|
file->cache.block = lfs->pcache.block;
|
|
|
|
file->cache.off = lfs->pcache.off;
|
2018-08-04 19:48:27 +00:00
|
|
|
file->cache.size = lfs->pcache.size;
|
2018-07-31 02:12:00 +00:00
|
|
|
lfs->pcache.block = 0xffffffff;
|
|
|
|
|
|
|
|
file->block = nblock;
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
relocate:
|
|
|
|
continue;
|
|
|
|
}
|
2018-04-11 00:55:17 +00:00
|
|
|
}
|
2018-04-08 21:58:12 +00:00
|
|
|
|
2018-04-11 00:55:17 +00:00
|
|
|
static int lfs_file_flush(lfs_t *lfs, lfs_file_t *file) {
|
|
|
|
if (file->flags & LFS_F_READING) {
|
|
|
|
file->flags &= ~LFS_F_READING;
|
2017-04-23 02:42:22 +00:00
|
|
|
}
|
|
|
|
|
2018-04-11 00:55:17 +00:00
|
|
|
if (file->flags & LFS_F_WRITING) {
|
|
|
|
lfs_off_t pos = file->pos;
|
2018-04-08 21:58:12 +00:00
|
|
|
|
2018-04-11 00:55:17 +00:00
|
|
|
if (!(file->flags & LFS_F_INLINE)) {
|
|
|
|
// copy over anything after current branch
|
|
|
|
lfs_file_t orig = {
|
2018-07-13 01:43:55 +00:00
|
|
|
.ctz.head = file->ctz.head,
|
|
|
|
.ctz.size = file->ctz.size,
|
2018-04-11 00:55:17 +00:00
|
|
|
.flags = LFS_O_RDONLY,
|
|
|
|
.pos = file->pos,
|
|
|
|
.cache = lfs->rcache,
|
|
|
|
};
|
2018-03-19 01:36:48 +00:00
|
|
|
lfs->rcache.block = 0xffffffff;
|
2017-04-24 04:39:50 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
while (file->pos < file->ctz.size) {
|
2018-03-19 01:36:48 +00:00
|
|
|
// 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;
|
|
|
|
}
|
2017-04-23 02:42:22 +00:00
|
|
|
|
2018-03-19 01:36:48 +00:00
|
|
|
res = lfs_file_write(lfs, file, &data, 1);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
2017-04-30 16:19:37 +00:00
|
|
|
|
2018-03-19 01:36:48 +00:00
|
|
|
// keep our reference to the rcache in sync
|
|
|
|
if (lfs->rcache.block != 0xffffffff) {
|
|
|
|
orig.cache.block = 0xffffffff;
|
|
|
|
lfs->rcache.block = 0xffffffff;
|
|
|
|
}
|
2017-04-30 16:19:37 +00:00
|
|
|
}
|
|
|
|
|
2018-03-19 01:36:48 +00:00
|
|
|
// write out what we have
|
|
|
|
while (true) {
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_flush(lfs,
|
|
|
|
&file->cache, &lfs->rcache, true);
|
2018-03-19 01:36:48 +00:00
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
|
|
|
return err;
|
2017-06-25 19:01:33 +00:00
|
|
|
}
|
|
|
|
|
2018-03-19 01:36:48 +00:00
|
|
|
break;
|
2018-07-31 02:12:00 +00:00
|
|
|
|
2017-06-25 19:01:33 +00:00
|
|
|
relocate:
|
2018-03-19 01:36:48 +00:00
|
|
|
LFS_DEBUG("Bad block at %d", file->block);
|
|
|
|
err = lfs_file_relocate(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-06-25 19:01:33 +00:00
|
|
|
}
|
2018-03-19 01:36:48 +00:00
|
|
|
} else {
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.size = lfs_max(file->pos, file->ctz.size);
|
2017-04-23 02:42:22 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 04:39:50 +00:00
|
|
|
// actual file updates
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.head = file->block;
|
|
|
|
file->ctz.size = file->pos;
|
2017-04-30 16:19:37 +00:00
|
|
|
file->flags &= ~LFS_F_WRITING;
|
|
|
|
file->flags |= LFS_F_DIRTY;
|
2017-04-24 04:39:50 +00:00
|
|
|
|
|
|
|
file->pos = pos;
|
|
|
|
}
|
2017-04-23 02:42:22 +00:00
|
|
|
|
2017-04-23 04:11:13 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-05-23 04:57:19 +00:00
|
|
|
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file) {
|
2018-07-31 02:12:00 +00:00
|
|
|
while (true) {
|
|
|
|
int err = lfs_file_flush(lfs, file);
|
2018-05-22 22:43:39 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
if ((file->flags & LFS_F_DIRTY) &&
|
|
|
|
!(file->flags & LFS_F_ERRED) &&
|
2018-08-01 15:24:59 +00:00
|
|
|
!lfs_pairisnull(file->m.pair)) {
|
2018-07-31 02:12:00 +00:00
|
|
|
// update dir entry
|
2018-08-01 23:10:24 +00:00
|
|
|
uint16_t type;
|
|
|
|
const void *buffer;
|
|
|
|
lfs_size_t size;
|
|
|
|
if (file->flags & LFS_F_INLINE) {
|
|
|
|
// inline the whole file
|
|
|
|
type = LFS_TYPE_INLINESTRUCT;
|
|
|
|
buffer = file->cache.buffer;
|
|
|
|
size = file->ctz.size;
|
|
|
|
} else {
|
|
|
|
// update the ctz reference
|
|
|
|
type = LFS_TYPE_CTZSTRUCT;
|
|
|
|
buffer = &file->ctz;
|
|
|
|
size = sizeof(file->ctz);
|
|
|
|
}
|
|
|
|
|
|
|
|
// commit file data and attributes
|
|
|
|
lfs_ctztole32(&file->ctz);
|
2018-08-01 15:24:59 +00:00
|
|
|
int err = lfs_dir_commit(lfs, &file->m,
|
2018-08-01 23:10:24 +00:00
|
|
|
LFS_MKATTR(type, file->id, buffer, size,
|
2018-07-30 20:15:05 +00:00
|
|
|
LFS_MKATTR(LFS_FROM_ATTRS, file->id, file->cfg->attrs, 0,
|
2018-08-01 23:10:24 +00:00
|
|
|
NULL)));
|
|
|
|
lfs_ctzfromle32(&file->ctz);
|
2018-05-23 04:57:19 +00:00
|
|
|
if (err) {
|
2018-07-31 02:12:00 +00:00
|
|
|
if (err == LFS_ERR_NOSPC && (file->flags & LFS_F_INLINE)) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
2018-05-23 04:57:19 +00:00
|
|
|
return err;
|
|
|
|
}
|
2018-07-31 02:12:00 +00:00
|
|
|
|
|
|
|
file->flags &= ~LFS_F_DIRTY;
|
2017-04-24 04:39:50 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
return 0;
|
2017-03-20 03:00:56 +00:00
|
|
|
|
2018-07-31 02:12:00 +00:00
|
|
|
relocate:
|
|
|
|
// inline file doesn't fit anymore
|
|
|
|
file->block = 0xfffffffe;
|
|
|
|
file->off = file->pos;
|
|
|
|
|
|
|
|
lfs_alloc_ack(lfs);
|
|
|
|
err = lfs_file_relocate(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
file->flags &= ~LFS_F_INLINE;
|
|
|
|
file->flags |= LFS_F_WRITING;
|
|
|
|
}
|
2017-03-20 03:00:56 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 02:40:03 +00:00
|
|
|
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
|
|
|
|
void *buffer, lfs_size_t size) {
|
|
|
|
uint8_t *data = buffer;
|
|
|
|
lfs_size_t nsize = size;
|
|
|
|
|
|
|
|
if ((file->flags & 3) == LFS_O_WRONLY) {
|
2018-02-04 20:36:36 +00:00
|
|
|
return LFS_ERR_BADF;
|
2017-04-24 02:40:03 +00:00
|
|
|
}
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
if (file->flags & LFS_F_WRITING) {
|
2017-04-24 04:39:50 +00:00
|
|
|
// flush out any writes
|
|
|
|
int err = lfs_file_flush(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (file->pos >= file->ctz.size) {
|
2017-09-17 22:57:12 +00:00
|
|
|
// eof if past end
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
size = lfs_min(size, file->ctz.size - file->pos);
|
2017-04-24 04:39:50 +00:00
|
|
|
nsize = size;
|
|
|
|
|
2017-04-24 02:40:03 +00:00
|
|
|
while (nsize > 0) {
|
|
|
|
// check if we need a new block
|
2017-04-30 16:19:37 +00:00
|
|
|
if (!(file->flags & LFS_F_READING) ||
|
|
|
|
file->off == lfs->cfg->block_size) {
|
2018-03-19 01:36:48 +00:00
|
|
|
if (!(file->flags & LFS_F_INLINE)) {
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctzfind(lfs, &file->cache, NULL,
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.head, file->ctz.size,
|
2018-03-17 15:28:14 +00:00
|
|
|
file->pos, &file->block, &file->off);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-03-19 01:36:48 +00:00
|
|
|
} else {
|
|
|
|
file->block = 0xfffffffe;
|
|
|
|
file->off = file->pos;
|
2017-04-24 02:40:03 +00:00
|
|
|
}
|
2017-04-30 16:19:37 +00:00
|
|
|
|
|
|
|
file->flags |= LFS_F_READING;
|
2017-04-24 02:40:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// read as much as we can in current block
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_read(lfs, NULL, &file->cache, true,
|
2017-04-30 16:19:37 +00:00
|
|
|
file->block, file->off, data, diff);
|
2017-04-24 02:40:03 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-04-24 04:39:50 +00:00
|
|
|
file->pos += diff;
|
2017-04-30 16:19:37 +00:00
|
|
|
file->off += diff;
|
2017-04-24 02:40:03 +00:00
|
|
|
data += diff;
|
|
|
|
nsize -= diff;
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2017-03-20 03:00:56 +00:00
|
|
|
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
|
|
|
|
const void *buffer, lfs_size_t size) {
|
|
|
|
const uint8_t *data = buffer;
|
|
|
|
lfs_size_t nsize = size;
|
|
|
|
|
2017-04-23 04:11:13 +00:00
|
|
|
if ((file->flags & 3) == LFS_O_RDONLY) {
|
2018-02-04 20:36:36 +00:00
|
|
|
return LFS_ERR_BADF;
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
2017-04-30 16:19:37 +00:00
|
|
|
if (file->flags & LFS_F_READING) {
|
2017-04-24 04:39:50 +00:00
|
|
|
// drop any reads
|
|
|
|
int err = lfs_file_flush(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if ((file->flags & LFS_O_APPEND) && file->pos < file->ctz.size) {
|
|
|
|
file->pos = file->ctz.size;
|
2017-04-24 04:39:50 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (!(file->flags & LFS_F_WRITING) && file->pos > file->ctz.size) {
|
2017-09-17 22:57:12 +00:00
|
|
|
// fill with zeros
|
|
|
|
lfs_off_t pos = file->pos;
|
2018-07-13 01:43:55 +00:00
|
|
|
file->pos = file->ctz.size;
|
2017-09-17 22:57:12 +00:00
|
|
|
|
|
|
|
while (file->pos < pos) {
|
|
|
|
lfs_ssize_t res = lfs_file_write(lfs, file, &(uint8_t){0}, 1);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
if ((file->flags & LFS_F_INLINE) &&
|
2018-07-30 20:15:05 +00:00
|
|
|
file->pos + nsize >= lfs->inline_size) {
|
2018-04-03 13:29:28 +00:00
|
|
|
// inline file doesn't fit anymore
|
2018-03-19 01:36:48 +00:00
|
|
|
file->block = 0xfffffffe;
|
|
|
|
file->off = file->pos;
|
|
|
|
|
|
|
|
lfs_alloc_ack(lfs);
|
2018-03-18 01:32:16 +00:00
|
|
|
int err = lfs_file_relocate(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
file->flags |= LFS_F_ERRED;
|
|
|
|
return err;
|
2018-03-17 15:28:14 +00:00
|
|
|
}
|
|
|
|
|
2018-03-18 01:32:16 +00:00
|
|
|
file->flags &= ~LFS_F_INLINE;
|
|
|
|
file->flags |= LFS_F_WRITING;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (nsize > 0) {
|
2017-04-23 02:42:22 +00:00
|
|
|
// check if we need a new block
|
2017-04-30 16:19:37 +00:00
|
|
|
if (!(file->flags & LFS_F_WRITING) ||
|
|
|
|
file->off == lfs->cfg->block_size) {
|
2018-03-19 01:36:48 +00:00
|
|
|
if (!(file->flags & LFS_F_INLINE)) {
|
2018-03-17 15:28:14 +00:00
|
|
|
if (!(file->flags & LFS_F_WRITING) && file->pos > 0) {
|
|
|
|
// find out which block we're extending from
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctzfind(lfs, &file->cache, NULL,
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.head, file->ctz.size,
|
2018-03-17 15:28:14 +00:00
|
|
|
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
|
|
|
|
file->cache.block = 0xffffffff;
|
|
|
|
}
|
|
|
|
|
|
|
|
// extend file with new blocks
|
|
|
|
lfs_alloc_ack(lfs);
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctzextend(lfs, &lfs->rcache, &file->cache,
|
2018-03-17 15:28:14 +00:00
|
|
|
file->block, file->pos,
|
|
|
|
&file->block, &file->off);
|
2017-04-23 04:11:13 +00:00
|
|
|
if (err) {
|
2017-11-16 23:25:41 +00:00
|
|
|
file->flags |= LFS_F_ERRED;
|
2017-04-23 04:11:13 +00:00
|
|
|
return err;
|
|
|
|
}
|
2018-03-19 01:36:48 +00:00
|
|
|
} else {
|
|
|
|
file->block = 0xfffffffe;
|
|
|
|
file->off = file->pos;
|
2017-03-20 03:00:56 +00:00
|
|
|
}
|
2017-09-17 17:53:18 +00:00
|
|
|
|
|
|
|
file->flags |= LFS_F_WRITING;
|
2017-03-20 03:00:56 +00:00
|
|
|
}
|
|
|
|
|
2017-04-23 02:42:22 +00:00
|
|
|
// program as much as we can in current block
|
2017-04-30 16:19:37 +00:00
|
|
|
lfs_size_t diff = lfs_min(nsize, lfs->cfg->block_size - file->off);
|
2017-05-14 17:01:45 +00:00
|
|
|
while (true) {
|
2018-08-04 19:48:27 +00:00
|
|
|
int err = lfs_cache_prog(lfs, &file->cache, &lfs->rcache, true,
|
2017-05-14 17:01:45 +00:00
|
|
|
file->block, file->off, data, diff);
|
|
|
|
if (err) {
|
|
|
|
if (err == LFS_ERR_CORRUPT) {
|
|
|
|
goto relocate;
|
|
|
|
}
|
2017-11-16 23:25:41 +00:00
|
|
|
file->flags |= LFS_F_ERRED;
|
2017-05-14 17:01:45 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
relocate:
|
2017-06-25 19:01:33 +00:00
|
|
|
err = lfs_file_relocate(lfs, file);
|
2017-05-14 17:01:45 +00:00
|
|
|
if (err) {
|
2017-11-16 23:25:41 +00:00
|
|
|
file->flags |= LFS_F_ERRED;
|
2017-05-14 17:01:45 +00:00
|
|
|
return err;
|
|
|
|
}
|
2017-03-20 03:00:56 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 04:39:50 +00:00
|
|
|
file->pos += diff;
|
2017-04-30 16:19:37 +00:00
|
|
|
file->off += diff;
|
2017-03-20 03:00:56 +00:00
|
|
|
data += diff;
|
|
|
|
nsize -= diff;
|
2017-05-14 17:01:45 +00:00
|
|
|
|
|
|
|
lfs_alloc_ack(lfs);
|
2017-04-23 02:42:22 +00:00
|
|
|
}
|
|
|
|
|
2017-11-16 23:25:41 +00:00
|
|
|
file->flags &= ~LFS_F_ERRED;
|
2017-03-20 03:00:56 +00:00
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2017-04-23 04:11:13 +00:00
|
|
|
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
|
|
|
|
lfs_soff_t off, int whence) {
|
|
|
|
// write out everything beforehand, may be noop if rdonly
|
|
|
|
int err = lfs_file_flush(lfs, file);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-04-24 04:39:50 +00:00
|
|
|
// update pos
|
|
|
|
if (whence == LFS_SEEK_SET) {
|
|
|
|
file->pos = off;
|
|
|
|
} else if (whence == LFS_SEEK_CUR) {
|
2018-01-03 21:00:04 +00:00
|
|
|
if (off < 0 && (lfs_off_t)-off > file->pos) {
|
2017-09-17 22:57:12 +00:00
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
|
|
|
|
2017-04-24 04:39:50 +00:00
|
|
|
file->pos = file->pos + off;
|
|
|
|
} else if (whence == LFS_SEEK_END) {
|
2018-07-13 01:43:55 +00:00
|
|
|
if (off < 0 && (lfs_off_t)-off > file->ctz.size) {
|
2017-09-17 22:57:12 +00:00
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
file->pos = file->ctz.size + off;
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
2017-09-27 00:50:39 +00:00
|
|
|
return file->pos;
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
2018-01-20 23:30:40 +00:00
|
|
|
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size) {
|
|
|
|
if ((file->flags & 3) == LFS_O_RDONLY) {
|
2018-02-04 20:36:36 +00:00
|
|
|
return LFS_ERR_BADF;
|
2018-01-20 23:30:40 +00:00
|
|
|
}
|
|
|
|
|
2018-02-04 19:10:07 +00:00
|
|
|
lfs_off_t oldsize = lfs_file_size(lfs, file);
|
|
|
|
if (size < oldsize) {
|
2018-01-20 23:30:40 +00:00
|
|
|
// 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
|
2018-07-13 20:04:31 +00:00
|
|
|
err = lfs_ctzfind(lfs, &file->cache, NULL,
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.head, file->ctz.size,
|
|
|
|
size, &file->ctz.head, &(lfs_off_t){0});
|
2018-01-20 23:30:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
file->ctz.size = size;
|
2018-01-20 23:30:40 +00:00
|
|
|
file->flags |= LFS_F_DIRTY;
|
2018-02-04 19:10:07 +00:00
|
|
|
} else if (size > oldsize) {
|
2018-01-20 23:30:40 +00:00
|
|
|
lfs_off_t pos = file->pos;
|
|
|
|
|
|
|
|
// flush+seek if not already at end
|
2018-02-04 19:10:07 +00:00
|
|
|
if (file->pos != oldsize) {
|
2018-01-29 21:20:12 +00:00
|
|
|
int err = lfs_file_seek(lfs, file, 0, LFS_SEEK_END);
|
2018-02-04 19:48:44 +00:00
|
|
|
if (err < 0) {
|
2018-01-20 23:30:40 +00:00
|
|
|
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
|
|
|
|
int err = lfs_file_seek(lfs, file, pos, LFS_SEEK_SET);
|
|
|
|
if (err < 0) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-04-23 04:11:13 +00:00
|
|
|
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file) {
|
2018-02-04 19:10:07 +00:00
|
|
|
(void)lfs;
|
2017-04-24 04:39:50 +00:00
|
|
|
return file->pos;
|
2017-04-23 04:11:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file) {
|
|
|
|
lfs_soff_t res = lfs_file_seek(lfs, file, 0, LFS_SEEK_SET);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-04-24 02:40:03 +00:00
|
|
|
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file) {
|
2018-02-04 19:10:07 +00:00
|
|
|
(void)lfs;
|
2018-01-20 23:30:40 +00:00
|
|
|
if (file->flags & LFS_F_WRITING) {
|
2018-07-13 01:43:55 +00:00
|
|
|
return lfs_max(file->pos, file->ctz.size);
|
2018-01-20 23:30:40 +00:00
|
|
|
} else {
|
2018-07-13 01:43:55 +00:00
|
|
|
return file->ctz.size;
|
2018-01-20 23:30:40 +00:00
|
|
|
}
|
2017-04-24 02:40:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-01-30 19:07:37 +00:00
|
|
|
/// General fs operations ///
|
2017-04-24 02:40:03 +00:00
|
|
|
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info) {
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t cwd;
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t tag = lfs_dir_lookup(lfs, &cwd, &path);
|
|
|
|
if (tag < 0) {
|
|
|
|
return tag;
|
2018-04-06 00:03:58 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 00:07:56 +00:00
|
|
|
return lfs_dir_getinfo(lfs, &cwd, lfs_tagid(tag), info);
|
2018-04-06 00:03:58 +00:00
|
|
|
}
|
|
|
|
|
2018-05-27 15:15:28 +00:00
|
|
|
int lfs_remove(lfs_t *lfs, const char *path) {
|
|
|
|
// deorphan if we haven't yet, needed at most once after poweron
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_forceconsistency(lfs);
|
2018-07-31 13:07:36 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t cwd;
|
2018-07-31 13:07:36 +00:00
|
|
|
err = lfs_dir_fetch(lfs, &cwd, lfs->root);
|
2018-05-27 15:15:28 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t tag = lfs_dir_lookup(lfs, &cwd, &path);
|
|
|
|
if (tag < 0) {
|
|
|
|
return tag;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-02 03:29:42 +00:00
|
|
|
lfs_mdir_t dir;
|
2018-07-13 00:07:56 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_DIR) {
|
2018-05-27 15:15:28 +00:00
|
|
|
// must be empty before removal
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_block_t pair[2];
|
|
|
|
int32_t res = lfs_dir_get(lfs, &cwd, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, lfs_tagid(tag), 8), pair);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
2018-07-09 17:51:31 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(pair);
|
2018-07-09 17:51:31 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir, pair);
|
2018-05-27 15:15:28 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dir.count > 0 || dir.split) {
|
|
|
|
return LFS_ERR_NOTEMPTY;
|
|
|
|
}
|
2018-07-31 13:07:36 +00:00
|
|
|
|
|
|
|
// mark fs as orphaned
|
|
|
|
lfs_globaldeorphaned(lfs, false);
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// delete the entry
|
2018-07-17 23:31:30 +00:00
|
|
|
err = lfs_dir_commit(lfs, &cwd,
|
|
|
|
LFS_MKATTR(LFS_TYPE_DELETE, lfs_tagid(tag), NULL, 0,
|
|
|
|
NULL));
|
2018-05-27 15:15:28 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-13 00:07:56 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_DIR) {
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_pred(lfs, dir.pair, &cwd);
|
2018-07-13 01:43:55 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// fix orphan
|
|
|
|
lfs_globaldeorphaned(lfs, true);
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
// steal state
|
2018-07-02 03:29:42 +00:00
|
|
|
cwd.tail[0] = dir.tail[0];
|
|
|
|
cwd.tail[1] = dir.tail[1];
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &dir.locals);
|
2018-07-13 00:07:56 +00:00
|
|
|
err = lfs_dir_commit(lfs, &cwd,
|
|
|
|
LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
|
2018-07-13 01:43:55 +00:00
|
|
|
cwd.tail, sizeof(cwd.tail),
|
|
|
|
NULL));
|
2018-07-13 00:07:56 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2018-05-27 15:15:28 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath) {
|
|
|
|
// deorphan if we haven't yet, needed at most once after poweron
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_forceconsistency(lfs);
|
2018-07-31 13:07:36 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// find old entry
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t oldcwd;
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t oldtag = lfs_dir_lookup(lfs, &oldcwd, &oldpath);
|
|
|
|
if (oldtag < 0) {
|
|
|
|
return oldtag;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// find new entry
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t newcwd;
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t prevtag = lfs_dir_lookup(lfs, &newcwd, &newpath);
|
|
|
|
if (prevtag < 0 && prevtag != LFS_ERR_NOENT) {
|
|
|
|
return prevtag;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 00:07:56 +00:00
|
|
|
uint16_t newid = lfs_tagid(prevtag);
|
2018-07-13 01:22:06 +00:00
|
|
|
//bool prevexists = (prevtag != LFS_ERR_NOENT);
|
2018-07-02 03:29:42 +00:00
|
|
|
//bool samepair = (lfs_paircmp(oldcwd.pair, newcwd.pair) == 0);
|
|
|
|
|
|
|
|
lfs_mdir_t prevdir;
|
2018-07-13 01:22:06 +00:00
|
|
|
if (prevtag != LFS_ERR_NOENT) {
|
2018-07-09 17:51:31 +00:00
|
|
|
// check that we have same type
|
2018-07-13 00:07:56 +00:00
|
|
|
if (lfs_tagtype(prevtag) != lfs_tagtype(oldtag)) {
|
2018-05-27 15:15:28 +00:00
|
|
|
return LFS_ERR_ISDIR;
|
|
|
|
}
|
|
|
|
|
2018-07-13 00:07:56 +00:00
|
|
|
if (lfs_tagtype(prevtag) == LFS_TYPE_DIR) {
|
2018-07-09 17:51:31 +00:00
|
|
|
// must be empty before removal
|
2018-07-13 01:43:55 +00:00
|
|
|
lfs_block_t prevpair[2];
|
|
|
|
int32_t res = lfs_dir_get(lfs, &newcwd, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, newid, 8), prevpair);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
2018-07-09 17:51:31 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(prevpair);
|
2018-07-09 17:51:31 +00:00
|
|
|
|
2018-05-27 15:15:28 +00:00
|
|
|
// must be empty before removal
|
2018-07-13 01:43:55 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &prevdir, prevpair);
|
2018-05-27 15:15:28 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (prevdir.count > 0 || prevdir.split) {
|
|
|
|
return LFS_ERR_NOTEMPTY;
|
|
|
|
}
|
2018-07-31 13:07:36 +00:00
|
|
|
|
|
|
|
// mark fs as orphaned
|
|
|
|
lfs_globaldeorphaned(lfs, false);
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// check that name fits
|
|
|
|
lfs_size_t nlen = strlen(newpath);
|
|
|
|
if (nlen > lfs->name_size) {
|
|
|
|
return LFS_ERR_NAMETOOLONG;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get next id
|
2018-07-17 23:31:30 +00:00
|
|
|
newid = newcwd.count;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-02 03:29:42 +00:00
|
|
|
// create move to fix later
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalmove(lfs, oldcwd.pair, lfs_tagid(oldtag));
|
2018-05-27 15:15:28 +00:00
|
|
|
|
2018-07-02 03:29:42 +00:00
|
|
|
// move over all attributes
|
2018-07-31 13:07:36 +00:00
|
|
|
err = lfs_dir_commit(lfs, &newcwd,
|
2018-07-13 00:07:56 +00:00
|
|
|
LFS_MKATTR(lfs_tagtype(oldtag), newid, newpath, strlen(newpath),
|
2018-07-13 01:43:55 +00:00
|
|
|
LFS_MKATTR(LFS_FROM_MOVE, newid, &oldcwd, lfs_tagid(oldtag),
|
|
|
|
NULL)));
|
2018-05-27 15:15:28 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
// let commit clean up after move (if we're different! otherwise move
|
|
|
|
// logic already fixed it for us)
|
|
|
|
if (lfs_paircmp(oldcwd.pair, newcwd.pair) != 0) {
|
|
|
|
err = lfs_dir_commit(lfs, &oldcwd, NULL);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:22:06 +00:00
|
|
|
if (prevtag != LFS_ERR_NOENT && lfs_tagtype(prevtag) == LFS_TYPE_DIR) {
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_pred(lfs, prevdir.pair, &newcwd);
|
2018-07-13 01:43:55 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
2018-07-02 03:29:42 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// fix orphan
|
|
|
|
lfs_globaldeorphaned(lfs, true);
|
|
|
|
|
2018-07-17 23:31:30 +00:00
|
|
|
// steal state
|
2018-07-02 03:29:42 +00:00
|
|
|
newcwd.tail[0] = prevdir.tail[0];
|
|
|
|
newcwd.tail[1] = prevdir.tail[1];
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_globalxor(&lfs->locals, &prevdir.locals);
|
2018-07-13 00:07:56 +00:00
|
|
|
err = lfs_dir_commit(lfs, &newcwd,
|
|
|
|
LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
|
2018-07-13 01:43:55 +00:00
|
|
|
newcwd.tail, sizeof(newcwd.tail),
|
|
|
|
NULL));
|
2018-07-13 00:07:56 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-27 15:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2018-04-06 00:03:58 +00:00
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
|
|
|
|
uint8_t type, void *buffer, lfs_size_t size) {
|
|
|
|
lfs_mdir_t cwd;
|
|
|
|
int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
res = lfs_dir_get(lfs, &cwd, 0x7ffff000,
|
|
|
|
LFS_MKTAG(0x100 | type, lfs_tagid(res),
|
|
|
|
lfs_min(size, lfs->attr_size)), buffer);
|
2018-08-05 00:23:49 +00:00
|
|
|
if (res < 0 && res != LFS_ERR_NOENT) {
|
2018-07-29 20:03:23 +00:00
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2018-08-05 00:23:49 +00:00
|
|
|
return (res == LFS_ERR_NOENT) ? 0 : lfs_tagsize(res);
|
2018-07-29 20:03:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_setattr(lfs_t *lfs, const char *path,
|
|
|
|
uint8_t type, const void *buffer, lfs_size_t size) {
|
|
|
|
if (size > lfs->attr_size) {
|
|
|
|
return LFS_ERR_NOSPC;
|
|
|
|
}
|
|
|
|
|
|
|
|
lfs_mdir_t cwd;
|
|
|
|
int32_t res = lfs_dir_lookup(lfs, &cwd, &path);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lfs_dir_commit(lfs, &cwd,
|
|
|
|
LFS_MKATTR(0x100 | type, lfs_tagid(res), buffer, size,
|
|
|
|
NULL));
|
|
|
|
}
|
|
|
|
|
2018-04-06 00:03:58 +00:00
|
|
|
|
2017-04-24 02:40:03 +00:00
|
|
|
/// Filesystem operations ///
|
2018-08-01 23:10:24 +00:00
|
|
|
static inline void lfs_superblockfromle32(lfs_superblock_t *superblock) {
|
|
|
|
superblock->version = lfs_fromle32(superblock->version);
|
|
|
|
superblock->block_size = lfs_fromle32(superblock->block_size);
|
|
|
|
superblock->block_count = lfs_fromle32(superblock->block_count);
|
|
|
|
superblock->inline_size = lfs_fromle32(superblock->inline_size);
|
|
|
|
superblock->attr_size = lfs_fromle32(superblock->attr_size);
|
|
|
|
superblock->name_size = lfs_fromle32(superblock->name_size);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void lfs_superblocktole32(lfs_superblock_t *superblock) {
|
|
|
|
superblock->version = lfs_tole32(superblock->version);
|
|
|
|
superblock->block_size = lfs_tole32(superblock->block_size);
|
|
|
|
superblock->block_count = lfs_tole32(superblock->block_count);
|
|
|
|
superblock->inline_size = lfs_tole32(superblock->inline_size);
|
|
|
|
superblock->attr_size = lfs_tole32(superblock->attr_size);
|
|
|
|
superblock->name_size = lfs_tole32(superblock->name_size);
|
|
|
|
}
|
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
static int lfs_init(lfs_t *lfs, const struct lfs_config *cfg) {
|
|
|
|
lfs->cfg = cfg;
|
|
|
|
|
2018-08-04 21:04:24 +00:00
|
|
|
// check that block size is a multiple of cache size is a multiple
|
|
|
|
// of prog and read sizes
|
|
|
|
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->read_size == 0);
|
|
|
|
LFS_ASSERT(lfs->cfg->cache_size % lfs->cfg->prog_size == 0);
|
|
|
|
LFS_ASSERT(lfs->cfg->block_size % lfs->cfg->cache_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);
|
|
|
|
|
2017-04-22 19:56:12 +00:00
|
|
|
// setup read cache
|
2017-04-30 16:54:27 +00:00
|
|
|
lfs->rcache.block = 0xffffffff;
|
2017-04-22 18:30:40 +00:00
|
|
|
if (lfs->cfg->read_buffer) {
|
|
|
|
lfs->rcache.buffer = lfs->cfg->read_buffer;
|
|
|
|
} else {
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs->rcache.buffer = lfs_malloc(lfs->cfg->cache_size);
|
2017-04-22 18:30:40 +00:00
|
|
|
if (!lfs->rcache.buffer) {
|
2017-04-24 03:10:16 +00:00
|
|
|
return LFS_ERR_NOMEM;
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-22 19:56:12 +00:00
|
|
|
// setup program cache
|
2017-04-30 16:54:27 +00:00
|
|
|
lfs->pcache.block = 0xffffffff;
|
2017-04-22 18:30:40 +00:00
|
|
|
if (lfs->cfg->prog_buffer) {
|
|
|
|
lfs->pcache.buffer = lfs->cfg->prog_buffer;
|
|
|
|
} else {
|
2018-08-04 19:48:27 +00:00
|
|
|
lfs->pcache.buffer = lfs_malloc(lfs->cfg->cache_size);
|
2017-04-22 18:30:40 +00:00
|
|
|
if (!lfs->pcache.buffer) {
|
2017-04-24 03:10:16 +00:00
|
|
|
return LFS_ERR_NOMEM;
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-04 21:04:24 +00:00
|
|
|
// setup lookahead, must be multiple of 32-bits
|
2018-01-29 21:20:12 +00:00
|
|
|
LFS_ASSERT(lfs->cfg->lookahead % 32 == 0);
|
|
|
|
LFS_ASSERT(lfs->cfg->lookahead > 0);
|
2017-04-22 19:56:12 +00:00
|
|
|
if (lfs->cfg->lookahead_buffer) {
|
2017-09-19 02:20:33 +00:00
|
|
|
lfs->free.buffer = lfs->cfg->lookahead_buffer;
|
2017-04-22 19:56:12 +00:00
|
|
|
} else {
|
2018-01-29 21:20:12 +00:00
|
|
|
lfs->free.buffer = lfs_malloc(lfs->cfg->lookahead/8);
|
2017-09-19 02:20:33 +00:00
|
|
|
if (!lfs->free.buffer) {
|
2017-04-24 03:10:16 +00:00
|
|
|
return LFS_ERR_NOMEM;
|
2017-04-22 19:56:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
// check that the size limits are sane
|
|
|
|
LFS_ASSERT(lfs->cfg->inline_size <= LFS_INLINE_MAX);
|
2018-08-04 21:04:24 +00:00
|
|
|
LFS_ASSERT(lfs->cfg->inline_size <= lfs->cfg->cache_size);
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
lfs->inline_size = lfs->cfg->inline_size;
|
|
|
|
if (!lfs->inline_size) {
|
2018-08-04 21:04:24 +00:00
|
|
|
lfs->inline_size = lfs_min(LFS_INLINE_MAX, lfs->cfg->cache_size);
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
}
|
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
LFS_ASSERT(lfs->cfg->attr_size <= LFS_ATTR_MAX);
|
|
|
|
lfs->attr_size = lfs->cfg->attr_size;
|
|
|
|
if (!lfs->attr_size) {
|
|
|
|
lfs->attr_size = LFS_ATTR_MAX;
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LFS_ASSERT(lfs->cfg->name_size <= LFS_NAME_MAX);
|
|
|
|
lfs->name_size = lfs->cfg->name_size;
|
|
|
|
if (!lfs->name_size) {
|
|
|
|
lfs->name_size = LFS_NAME_MAX;
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// setup default state
|
|
|
|
lfs->root[0] = 0xffffffff;
|
|
|
|
lfs->root[1] = 0xffffffff;
|
2018-08-01 15:24:59 +00:00
|
|
|
lfs->mlist = NULL;
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs->globals.s.movepair[0] = 0xffffffff;
|
|
|
|
lfs->globals.s.movepair[1] = 0xffffffff;
|
|
|
|
lfs->globals.s.moveid = 0x3ff;
|
|
|
|
lfs->globals.s.deorphaned = true;
|
2018-08-01 06:05:04 +00:00
|
|
|
lfs_globalzero(&lfs->locals);
|
2017-04-29 15:22:01 +00:00
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lfs_deinit(lfs_t *lfs) {
|
2017-05-14 17:01:45 +00:00
|
|
|
// free allocated memory
|
2017-04-22 18:30:40 +00:00
|
|
|
if (!lfs->cfg->read_buffer) {
|
2018-01-29 21:20:12 +00:00
|
|
|
lfs_free(lfs->rcache.buffer);
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!lfs->cfg->prog_buffer) {
|
2018-01-29 21:20:12 +00:00
|
|
|
lfs_free(lfs->pcache.buffer);
|
2017-04-22 18:30:40 +00:00
|
|
|
}
|
|
|
|
|
2017-04-29 17:50:23 +00:00
|
|
|
if (!lfs->cfg->lookahead_buffer) {
|
2018-01-29 21:20:12 +00:00
|
|
|
lfs_free(lfs->free.buffer);
|
2017-04-29 17:50:23 +00:00
|
|
|
}
|
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_format(lfs_t *lfs, const struct lfs_config *cfg) {
|
|
|
|
int err = lfs_init(lfs, cfg);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-02-27 00:05:27 +00:00
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// create free lookahead
|
2017-11-10 01:10:08 +00:00
|
|
|
memset(lfs->free.buffer, 0, lfs->cfg->lookahead/8);
|
2017-04-22 19:56:12 +00:00
|
|
|
lfs->free.off = 0;
|
2018-04-10 20:14:27 +00:00
|
|
|
lfs->free.size = lfs_min(lfs->cfg->lookahead, lfs->cfg->block_count);
|
|
|
|
lfs->free.i = 0;
|
2018-02-08 07:30:21 +00:00
|
|
|
lfs_alloc_ack(lfs);
|
2017-03-20 03:00:56 +00:00
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// create superblock dir
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t dir;
|
2018-08-01 15:24:59 +00:00
|
|
|
err = lfs_dir_alloc(lfs, &dir);
|
2017-04-18 03:27:06 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// write root directory
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t root;
|
2018-08-01 15:24:59 +00:00
|
|
|
err = lfs_dir_alloc(lfs, &root);
|
2017-04-18 03:27:06 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-05-29 00:49:20 +00:00
|
|
|
err = lfs_dir_commit(lfs, &root, NULL);
|
2017-03-25 21:20:31 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-02-27 00:05:27 +00:00
|
|
|
}
|
2017-04-18 03:27:06 +00:00
|
|
|
|
2017-03-25 23:11:45 +00:00
|
|
|
lfs->root[0] = root.pair[0];
|
|
|
|
lfs->root[1] = root.pair[1];
|
2018-05-21 05:56:20 +00:00
|
|
|
dir.tail[0] = lfs->root[0];
|
|
|
|
dir.tail[1] = lfs->root[1];
|
2017-03-25 21:20:31 +00:00
|
|
|
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
// write one superblock
|
2018-05-26 18:50:06 +00:00
|
|
|
lfs_superblock_t superblock = {
|
2018-05-21 05:56:20 +00:00
|
|
|
.magic = {"littlefs"},
|
|
|
|
.version = LFS_DISK_VERSION,
|
|
|
|
|
|
|
|
.block_size = lfs->cfg->block_size,
|
|
|
|
.block_count = lfs->cfg->block_count,
|
2018-07-29 20:03:23 +00:00
|
|
|
.attr_size = lfs->attr_size,
|
2018-05-27 15:15:28 +00:00
|
|
|
.name_size = lfs->name_size,
|
2018-08-04 21:04:24 +00:00
|
|
|
.inline_size = lfs->inline_size,
|
2018-05-21 05:56:20 +00:00
|
|
|
};
|
|
|
|
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_superblocktole32(&superblock);
|
|
|
|
lfs_pairtole32(lfs->root);
|
2018-07-13 00:07:56 +00:00
|
|
|
err = lfs_dir_commit(lfs, &dir,
|
|
|
|
LFS_MKATTR(LFS_TYPE_SUPERBLOCK, 0, &superblock, sizeof(superblock),
|
|
|
|
LFS_MKATTR(LFS_TYPE_DIRSTRUCT, 0, lfs->root, sizeof(lfs->root),
|
2018-07-13 01:43:55 +00:00
|
|
|
NULL)));
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(lfs->root);
|
|
|
|
lfs_superblockfromle32(&superblock);
|
2018-03-23 21:11:36 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-02-27 00:05:27 +00:00
|
|
|
}
|
|
|
|
|
2017-04-18 03:27:06 +00:00
|
|
|
// sanity check that fetch works
|
2018-05-26 18:50:06 +00:00
|
|
|
err = lfs_dir_fetch(lfs, &dir, (const lfs_block_t[2]){0, 1});
|
2017-04-22 18:30:40 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lfs_deinit(lfs);
|
2017-02-27 00:05:27 +00:00
|
|
|
}
|
|
|
|
|
2017-04-22 18:30:40 +00:00
|
|
|
int lfs_mount(lfs_t *lfs, const struct lfs_config *cfg) {
|
|
|
|
int err = lfs_init(lfs, cfg);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2017-03-13 00:41:08 +00:00
|
|
|
|
2017-04-22 19:56:12 +00:00
|
|
|
// setup free lookahead
|
2018-02-08 07:30:21 +00:00
|
|
|
lfs->free.off = 0;
|
2018-04-10 20:14:27 +00:00
|
|
|
lfs->free.size = 0;
|
|
|
|
lfs->free.i = 0;
|
2018-02-08 07:30:21 +00:00
|
|
|
lfs_alloc_ack(lfs);
|
2017-04-22 19:56:12 +00:00
|
|
|
|
|
|
|
// load superblock
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs_mdir_t superdir;
|
|
|
|
err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
if (err) {
|
2017-10-07 21:56:00 +00:00
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
lfs_superblock_t superblock;
|
2018-08-04 00:01:27 +00:00
|
|
|
int32_t res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
|
2018-07-13 00:07:56 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_SUPERBLOCK, 0, sizeof(superblock)),
|
2018-07-13 01:22:06 +00:00
|
|
|
&superblock);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_superblockfromle32(&superblock);
|
2018-03-23 21:11:36 +00:00
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
if (memcmp(superblock.magic, "littlefs", 8) != 0) {
|
2018-08-04 21:04:24 +00:00
|
|
|
LFS_ERROR("Invalid superblock \"%.8s\"", superblock.magic);
|
|
|
|
return LFS_ERR_INVAL;
|
2017-03-13 00:41:08 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
uint16_t major_version = (0xffff & (superblock.version >> 16));
|
|
|
|
uint16_t minor_version = (0xffff & (superblock.version >> 0));
|
2018-01-26 20:26:25 +00:00
|
|
|
if ((major_version != LFS_DISK_VERSION_MAJOR ||
|
|
|
|
minor_version > LFS_DISK_VERSION_MINOR)) {
|
|
|
|
LFS_ERROR("Invalid version %d.%d", major_version, minor_version);
|
2017-04-24 03:10:16 +00:00
|
|
|
return LFS_ERR_INVAL;
|
2017-04-18 03:27:06 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 00:01:27 +00:00
|
|
|
res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
|
2018-07-13 00:07:56 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, sizeof(lfs->root)),
|
2018-07-13 01:22:06 +00:00
|
|
|
&lfs->root);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
2018-07-09 17:51:31 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(lfs->root);
|
2018-07-09 17:51:31 +00:00
|
|
|
|
2018-08-04 21:04:24 +00:00
|
|
|
// check superblock configuration
|
2018-07-29 20:03:23 +00:00
|
|
|
if (superblock.attr_size) {
|
|
|
|
if (superblock.attr_size > lfs->attr_size) {
|
|
|
|
LFS_ERROR("Unsupported attr size (%d > %d)",
|
|
|
|
superblock.attr_size, lfs->attr_size);
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
|
|
|
|
2018-07-29 20:03:23 +00:00
|
|
|
lfs->attr_size = superblock.attr_size;
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
}
|
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
if (superblock.name_size) {
|
|
|
|
if (superblock.name_size > lfs->name_size) {
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
LFS_ERROR("Unsupported name size (%d > %d)",
|
2018-05-21 05:56:20 +00:00
|
|
|
superblock.name_size, lfs->name_size);
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
lfs->name_size = superblock.name_size;
|
Added disk-backed limits on the name/attrs/inline sizes
Being a portable, microcontroller-scale embedded filesystem, littlefs is
presented with a relatively unique challenge. The amount of RAM
available is on completely different scales from machine to machine, and
what is normally a reasonable RAM assumption may break completely on an
embedded system.
A great example of this is file names. On almost every PC these days, the limit
for a file name is 255 bytes. It's a very convenient limit for a number
of reasons. However, on microcontrollers, allocating 255 bytes of RAM to
do a file search can be unreasonable.
The simplest solution (and one that has existing in littlefs for a
while), is to let this limit be redefined to a smaller value on devices
that need to save RAM. However, this presents an interesting portability
issue. If these devices are plugged into a PC with relatively infinite
RAM, nothing stops the PC from writing files with full 255-byte file
names, which can't be read on the small device.
One solution here is to store this limit on the superblock during format
time. When mounting a disk, the filesystem implementation is responsible for
checking this limit in the superblock. If it's larger than what can be
read, raise an error. If it's smaller, respect the limit on the
superblock and raise an error if the user attempts to exceed it.
In this commit, this strategy is adopted for file names, inline files,
and the size of all attributes, since these could impact the memory
consumption of the filesystem. (Recording the attribute's limit is
iffy, but is the only other arbitrary limit and could be used for disabling
support of custom attributes).
Note! This changes makes it very important to configure littlefs
correctly at format time. If littlefs is formatted on a PC without
changing the limits appropriately, it will be rejected by a smaller
device.
2018-04-01 20:36:29 +00:00
|
|
|
}
|
|
|
|
|
2018-08-04 21:04:24 +00:00
|
|
|
if (superblock.inline_size) {
|
|
|
|
if (superblock.inline_size > lfs->inline_size) {
|
|
|
|
LFS_ERROR("Unsupported inline size (%d > %d)",
|
|
|
|
superblock.inline_size, lfs->inline_size);
|
|
|
|
return LFS_ERR_INVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
lfs->inline_size = superblock.inline_size;
|
|
|
|
}
|
|
|
|
|
2018-08-01 06:05:04 +00:00
|
|
|
// scan for any global updates
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs_mdir_t dir = {.tail = {0, 1}};
|
|
|
|
while (!lfs_pairisnull(dir.tail)) {
|
|
|
|
int err = lfs_dir_fetch(lfs, &dir, dir.tail);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
// xor together indirect deletes
|
|
|
|
lfs_globalxor(&lfs->locals, &dir.locals);
|
|
|
|
}
|
|
|
|
|
|
|
|
// update littlefs with globals
|
|
|
|
lfs_globalfromle32(&lfs->locals);
|
|
|
|
lfs_globalxor(&lfs->globals, &lfs->locals);
|
|
|
|
lfs_globalzero(&lfs->locals);
|
|
|
|
if (!lfs_pairisnull(lfs->globals.s.movepair)) {
|
|
|
|
LFS_DEBUG("Found move %d %d %d",
|
|
|
|
lfs->globals.s.movepair[0],
|
|
|
|
lfs->globals.s.movepair[1],
|
|
|
|
lfs->globals.s.moveid);
|
2018-07-02 03:29:42 +00:00
|
|
|
}
|
|
|
|
|
2017-10-07 21:56:00 +00:00
|
|
|
return 0;
|
2017-04-01 15:44:17 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_unmount(lfs_t *lfs) {
|
2017-04-22 18:30:40 +00:00
|
|
|
return lfs_deinit(lfs);
|
2017-04-01 15:44:17 +00:00
|
|
|
}
|
|
|
|
|
2017-04-24 02:40:03 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
/// Filesystem filesystem operations ///
|
2018-05-26 18:50:06 +00:00
|
|
|
int lfs_fs_traverse(lfs_t *lfs,
|
2018-07-30 19:40:27 +00:00
|
|
|
int (*cb)(void *data, lfs_block_t block), void *data) {
|
2018-05-21 05:56:20 +00:00
|
|
|
if (lfs_pairisnull(lfs->root)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over metadata pairs
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t dir = {.tail = {0, 1}};
|
2018-05-21 05:56:20 +00:00
|
|
|
while (!lfs_pairisnull(dir.tail)) {
|
|
|
|
for (int i = 0; i < 2; i++) {
|
2018-07-30 19:40:27 +00:00
|
|
|
int err = cb(data, dir.tail[i]);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate through ids in directory
|
2018-05-26 18:50:06 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &dir, dir.tail);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
for (uint16_t id = 0; id < dir.count; id++) {
|
2018-07-13 01:43:55 +00:00
|
|
|
struct lfs_ctz ctz;
|
|
|
|
int32_t tag = lfs_dir_get(lfs, &dir, 0x7c3ff000,
|
|
|
|
LFS_MKTAG(LFS_TYPE_STRUCT, id, sizeof(ctz)), &ctz);
|
|
|
|
if (tag < 0) {
|
|
|
|
if (tag == LFS_ERR_NOENT) {
|
2018-05-21 05:56:20 +00:00
|
|
|
continue;
|
|
|
|
}
|
2018-07-13 01:43:55 +00:00
|
|
|
return tag;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_ctzfromle32(&ctz);
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (lfs_tagtype(tag) == LFS_TYPE_CTZSTRUCT) {
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctztraverse(lfs, &lfs->rcache, NULL,
|
2018-07-13 01:43:55 +00:00
|
|
|
ctz.head, ctz.size, cb, data);
|
2018-05-26 18:50:06 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// iterate over any open files
|
2018-08-01 15:24:59 +00:00
|
|
|
for (lfs_file_t *f = (lfs_file_t*)lfs->mlist; f; f = f->next) {
|
|
|
|
if (f->type != LFS_TYPE_REG) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2018-05-21 05:56:20 +00:00
|
|
|
if ((f->flags & LFS_F_DIRTY) && !(f->flags & LFS_F_INLINE)) {
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctztraverse(lfs, &lfs->rcache, &f->cache,
|
2018-07-13 01:43:55 +00:00
|
|
|
f->ctz.head, f->ctz.size, cb, data);
|
2018-05-26 18:50:06 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ((f->flags & LFS_F_WRITING) && !(f->flags & LFS_F_INLINE)) {
|
2018-07-13 20:04:31 +00:00
|
|
|
int err = lfs_ctztraverse(lfs, &lfs->rcache, &f->cache,
|
2018-05-26 18:50:06 +00:00
|
|
|
f->block, f->pos, cb, data);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2017-04-01 15:44:17 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_pred(lfs_t *lfs,
|
|
|
|
const lfs_block_t pair[2], lfs_mdir_t *pdir) {
|
2018-07-30 14:10:04 +00:00
|
|
|
if (lfs_pairisnull(lfs->root)) {
|
|
|
|
return LFS_ERR_NOENT;
|
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
// iterate over all directory directory entries
|
2018-05-21 05:56:20 +00:00
|
|
|
pdir->tail[0] = 0;
|
|
|
|
pdir->tail[1] = 1;
|
|
|
|
while (!lfs_pairisnull(pdir->tail)) {
|
|
|
|
if (lfs_paircmp(pdir->tail, pair) == 0) {
|
2018-07-13 01:43:55 +00:00
|
|
|
return 0;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-05-26 18:50:06 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, pdir, pdir->tail);
|
2018-05-21 05:56:20 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
return LFS_ERR_NOENT;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
Introduced xored-globals logic to fix fundamental problem with moves
This was a big roadblock for a while: with the new feature of inlined
files, the existing move logic was fundamentally flawed.
To pull off atomic moves between two different metadata-pairs, littlefs
uses a simple, if a bit clumsy trick.
1. Marks entry as "moving"
2. Copies entry to new metadata-pair
3. Deletes old entry
If power is lost before the move operation is completed, we will find the
"moving" tag. This means there may or may not be an incomplete move on
the filesystem. In this case, we simply search for the moved entry, if
we find it, we remove the old entry, otherwise we just remove the
"moving" tag.
This worked perfectly, until we introduced inlined files. See, unlike
the existing directory and ctz entries, inlined files have no guarantee
they are unique. There is nothing we can search for that will allow us
to find a moved file unless we assign entries globally-unique ids. (note
that moves are fundamentally rename operations, so searching for names
does not make sense).
---
Solving this problem required completely restructuring how littlefs
handled moves and pulled out a really old idea that had been left in the
cutting room floor back when littlefs was going through many
designs: xored-globals.
The problem xored-globals solves is the need to maintain some global state
via commits to these distributed, independent metadata-pairs. The idea
is that we can use some sort of symmetric operation, such as xor, to
introduces deltas of the global state that can be committed atomically
along with any other info to these metadata-pairs.
This means that to figure out our global state, we xor together the global
delta stored in every metadata-pair.
Which means any commit can update the global state atomically, opening
up a whole new set atomic possibilities.
There is a couple of downsides. These globals may end up with deltas on
every single metadata-pair, effectively duplicating the data for each
block. Additionally, these globals need to have multiple copies in RAM.
This means and globals need to be a bounded size and very small, since even
small globals will have a large footprint.
---
On top of xored-globals, it's trivial to fix our move logic. Here we've
added an indirect delete tag which allows us to atomically specify a
delete of any entry on the filesystem.
Our move operation is now:
1. Copy entry to new metadata-pair and atomically xor globals to
indirectly delete our original entry.
2. Delete the original entry and xor globals to remove the indirect
delete.
Extra exciting is that this now takes our relatively clumsy move
operation into a sexy guaranteed O(1) move operation with no searching
necessary (though we do need to xor globals during mount).
Also reintroduced entry struct, now with a specific purpose to describe
the metadata-pair + id combo needed by indirect deletes to locate an
entry.
2018-05-29 17:35:23 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
static int32_t lfs_fs_parent(lfs_t *lfs, const lfs_block_t pair[2],
|
2018-07-13 01:22:06 +00:00
|
|
|
lfs_mdir_t *parent) {
|
2018-07-30 14:10:04 +00:00
|
|
|
if (lfs_pairisnull(lfs->root)) {
|
|
|
|
return LFS_ERR_NOENT;
|
|
|
|
}
|
|
|
|
|
2018-07-09 22:29:40 +00:00
|
|
|
// search for both orderings so we can reuse the find function
|
|
|
|
lfs_block_t child[2] = {pair[0], pair[1]};
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairtole32(child);
|
2018-07-09 22:29:40 +00:00
|
|
|
for (int i = 0; i < 2; i++) {
|
|
|
|
// iterate over all directory directory entries
|
|
|
|
parent->tail[0] = 0;
|
|
|
|
parent->tail[1] = 1;
|
|
|
|
while (!lfs_pairisnull(parent->tail)) {
|
2018-07-13 01:22:06 +00:00
|
|
|
int32_t tag = lfs_dir_find(lfs, parent, parent->tail, 0x7fc00fff,
|
2018-07-13 00:07:56 +00:00
|
|
|
LFS_MKTAG(LFS_TYPE_DIRSTRUCT, 0, sizeof(child)),
|
2018-07-13 01:22:06 +00:00
|
|
|
child);
|
|
|
|
if (tag != LFS_ERR_NOENT) {
|
|
|
|
return tag;
|
2017-04-01 17:23:15 +00:00
|
|
|
}
|
|
|
|
}
|
2018-07-09 22:29:40 +00:00
|
|
|
|
|
|
|
lfs_pairswap(child);
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
2017-04-01 17:23:15 +00:00
|
|
|
|
2018-07-11 12:18:30 +00:00
|
|
|
return LFS_ERR_NOENT;
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
2018-05-29 05:50:47 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_relocate(lfs_t *lfs,
|
2018-08-01 23:10:24 +00:00
|
|
|
const lfs_block_t oldpair[2], lfs_block_t newpair[2]) {
|
2018-08-01 15:24:59 +00:00
|
|
|
// update internal root
|
|
|
|
if (lfs_paircmp(oldpair, lfs->root) == 0) {
|
|
|
|
LFS_DEBUG("Relocating root %d %d", newpair[0], newpair[1]);
|
|
|
|
lfs->root[0] = newpair[0];
|
|
|
|
lfs->root[1] = newpair[1];
|
|
|
|
}
|
|
|
|
|
|
|
|
// update internally tracked dirs
|
|
|
|
for (lfs_mlist_t *d = lfs->mlist; d; d = d->next) {
|
|
|
|
if (lfs_paircmp(oldpair, d->m.pair) == 0) {
|
|
|
|
d->m.pair[0] = newpair[0];
|
|
|
|
d->m.pair[1] = newpair[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
// find parent
|
2018-05-29 06:11:26 +00:00
|
|
|
lfs_mdir_t parent;
|
2018-08-01 10:52:48 +00:00
|
|
|
int32_t tag = lfs_fs_parent(lfs, oldpair, &parent);
|
2018-07-13 01:43:55 +00:00
|
|
|
if (tag < 0 && tag != LFS_ERR_NOENT) {
|
|
|
|
return tag;
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 01:43:55 +00:00
|
|
|
if (tag != LFS_ERR_NOENT) {
|
2017-05-14 17:01:45 +00:00
|
|
|
// update disk, this creates a desync
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairtole32(newpair);
|
2018-07-13 01:43:55 +00:00
|
|
|
int err = lfs_dir_commit(lfs, &parent,
|
|
|
|
&(lfs_mattr_t){.tag=tag, .buffer=newpair});
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(newpair);
|
2017-05-14 17:01:45 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2017-04-29 17:41:53 +00:00
|
|
|
}
|
2017-05-14 17:01:45 +00:00
|
|
|
|
|
|
|
// clean up bad block, which should now be a desync
|
2018-08-01 10:52:48 +00:00
|
|
|
return lfs_fs_forceconsistency(lfs);
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// find pred
|
2018-08-01 10:52:48 +00:00
|
|
|
int err = lfs_fs_pred(lfs, oldpair, &parent);
|
2018-07-13 01:43:55 +00:00
|
|
|
if (err && err != LFS_ERR_NOENT) {
|
|
|
|
return err;
|
2017-05-14 17:01:45 +00:00
|
|
|
}
|
|
|
|
|
2018-07-13 20:04:31 +00:00
|
|
|
// if we can't find dir, it must be new
|
2018-07-13 01:43:55 +00:00
|
|
|
if (err != LFS_ERR_NOENT) {
|
2017-05-14 17:01:45 +00:00
|
|
|
// just replace bad pair, no desync can occur
|
2018-05-26 18:50:06 +00:00
|
|
|
parent.tail[0] = newpair[0];
|
|
|
|
parent.tail[1] = newpair[1];
|
2018-07-13 00:07:56 +00:00
|
|
|
int err = lfs_dir_commit(lfs, &parent,
|
|
|
|
LFS_MKATTR(LFS_TYPE_TAIL + parent.split, 0x3ff,
|
2018-08-01 23:10:24 +00:00
|
|
|
parent.tail, sizeof(parent.tail),
|
2018-07-13 01:43:55 +00:00
|
|
|
NULL));
|
2018-05-29 05:50:47 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-14 17:01:45 +00:00
|
|
|
return 0;
|
2017-04-14 22:33:36 +00:00
|
|
|
}
|
2017-04-01 17:23:15 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
static int lfs_fs_forceconsistency(lfs_t *lfs) {
|
2018-08-04 00:01:27 +00:00
|
|
|
if (!lfs->globals.s.deorphaned) {
|
2018-08-01 10:52:48 +00:00
|
|
|
LFS_DEBUG("Found orphans %d",
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs->globals.s.deorphaned);
|
2018-08-01 10:52:48 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// Fix any orphans
|
|
|
|
lfs_mdir_t pdir = {.split = true};
|
|
|
|
lfs_mdir_t dir = {.tail = {0, 1}};
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// iterate over all directory directory entries
|
|
|
|
while (!lfs_pairisnull(dir.tail)) {
|
|
|
|
int err = lfs_dir_fetch(lfs, &dir, dir.tail);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-07-17 23:31:30 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// check head blocks for orphans
|
|
|
|
if (!pdir.split) {
|
|
|
|
// check if we have a parent
|
|
|
|
lfs_mdir_t parent;
|
2018-08-01 10:52:48 +00:00
|
|
|
int32_t tag = lfs_fs_parent(lfs, pdir.tail, &parent);
|
2018-07-31 13:07:36 +00:00
|
|
|
if (tag < 0 && tag != LFS_ERR_NOENT) {
|
|
|
|
return tag;
|
|
|
|
}
|
2018-07-17 23:31:30 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
if (tag == LFS_ERR_NOENT) {
|
|
|
|
// we are an orphan
|
2018-08-01 10:52:48 +00:00
|
|
|
LFS_DEBUG("Fixing orphan %d %d",
|
2018-07-31 13:07:36 +00:00
|
|
|
pdir.tail[0], pdir.tail[1]);
|
|
|
|
|
|
|
|
pdir.tail[0] = dir.tail[0];
|
|
|
|
pdir.tail[1] = dir.tail[1];
|
|
|
|
err = lfs_dir_commit(lfs, &pdir,
|
|
|
|
LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
|
|
|
|
pdir.tail, sizeof(pdir.tail),
|
|
|
|
NULL));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-07-02 03:29:42 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
break;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
lfs_block_t pair[2];
|
|
|
|
int32_t res = lfs_dir_get(lfs, &parent, 0x7ffff000, tag, pair);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
2018-08-01 23:10:24 +00:00
|
|
|
lfs_pairfromle32(pair);
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
if (!lfs_pairsync(pair, pdir.tail)) {
|
|
|
|
// we have desynced
|
2018-08-01 10:52:48 +00:00
|
|
|
LFS_DEBUG("Fixing half-orphan %d %d", pair[0], pair[1]);
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
pdir.tail[0] = pair[0];
|
|
|
|
pdir.tail[1] = pair[1];
|
|
|
|
err = lfs_dir_commit(lfs, &pdir,
|
|
|
|
LFS_MKATTR(LFS_TYPE_SOFTTAIL, 0x3ff,
|
|
|
|
pdir.tail, sizeof(pdir.tail),
|
|
|
|
NULL));
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
break;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
memcpy(&pdir, &dir, sizeof(pdir));
|
|
|
|
}
|
2018-07-11 12:18:30 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// mark orphan as fixed
|
|
|
|
lfs_globaldeorphaned(lfs, false);
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-08-04 00:01:27 +00:00
|
|
|
if (lfs->globals.s.moveid != 0x3ff) {
|
2018-07-31 13:07:36 +00:00
|
|
|
// Fix bad moves
|
2018-08-01 10:52:48 +00:00
|
|
|
LFS_DEBUG("Fixing move %d %d %d",
|
2018-08-04 00:01:27 +00:00
|
|
|
lfs->globals.s.movepair[0],
|
|
|
|
lfs->globals.s.movepair[1],
|
|
|
|
lfs->globals.s.moveid);
|
2018-05-21 05:56:20 +00:00
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// fetch and delete the moved entry
|
|
|
|
lfs_mdir_t movedir;
|
2018-08-04 00:01:27 +00:00
|
|
|
int err = lfs_dir_fetch(lfs, &movedir, lfs->globals.s.movepair);
|
2018-07-31 13:07:36 +00:00
|
|
|
if (err) {
|
|
|
|
return err;
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
2018-07-31 13:07:36 +00:00
|
|
|
// rely on cancel logic inside commit
|
|
|
|
err = lfs_dir_commit(lfs, &movedir, NULL);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
2018-05-21 05:56:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2018-04-08 21:58:12 +00:00
|
|
|
|
2018-08-01 10:52:48 +00:00
|
|
|
lfs_ssize_t lfs_fs_getattr(lfs_t *lfs,
|
|
|
|
uint8_t type, void *buffer, lfs_size_t size) {
|
|
|
|
lfs_mdir_t superdir;
|
|
|
|
int err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
int32_t res = lfs_dir_get(lfs, &superdir, 0x7ffff000,
|
|
|
|
LFS_MKTAG(0x100 | type, 0,
|
|
|
|
lfs_min(size, lfs->attr_size)), buffer);
|
|
|
|
if (res < 0) {
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2018-08-05 00:23:49 +00:00
|
|
|
return (res == LFS_ERR_NOENT) ? 0 : lfs_tagsize(res);
|
2018-08-01 10:52:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
int lfs_fs_setattr(lfs_t *lfs,
|
|
|
|
uint8_t type, const void *buffer, lfs_size_t size) {
|
|
|
|
if (size > lfs->attr_size) {
|
|
|
|
return LFS_ERR_NOSPC;
|
|
|
|
}
|
|
|
|
|
|
|
|
lfs_mdir_t superdir;
|
|
|
|
int err = lfs_dir_fetch(lfs, &superdir, (const lfs_block_t[2]){0, 1});
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lfs_dir_commit(lfs, &superdir,
|
|
|
|
LFS_MKATTR(0x100 | type, 0, buffer, size,
|
|
|
|
NULL));
|
|
|
|
}
|
|
|
|
|
2018-07-30 19:40:27 +00:00
|
|
|
static int lfs_fs_size_count(void *p, lfs_block_t block) {
|
2018-07-30 14:10:04 +00:00
|
|
|
lfs_size_t *size = p;
|
|
|
|
*size += 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
lfs_ssize_t lfs_fs_size(lfs_t *lfs) {
|
|
|
|
lfs_size_t size = 0;
|
|
|
|
int err = lfs_fs_traverse(lfs, lfs_fs_size_count, &size);
|
|
|
|
if (err) {
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|