fs/mnemofs: Add mnemofs version 1 support.

Split mnemofs into allocation, directory, file, CTZ, and read/write
modules, and update direntry traversal and file handling. Add superblock
format version 1 support, reject newer on-flash versions, and preserve
the mounted version when rewriting metadata.

Update the NAND simulator drivers for the new mnemofs behavior by fixing
spare writes, exposing the erase state, allowing raw reads, and documenting
the background-task startup flow.

Signed-off-by: Saurav Pal <resyfer.dev@gmail.com>
This commit is contained in:
Saurav Pal 2026-05-15 23:14:23 +05:30 committed by Xiang Xiao
parent 319ca656b8
commit 4f6e695f7c
19 changed files with 9246 additions and 9597 deletions

View file

@ -49,15 +49,6 @@
# ##############################################################################
if(CONFIG_FS_MNEMOFS)
target_sources(
fs
PRIVATE mnemofs_blkalloc.c
mnemofs_ctz.c
mnemofs_fsobj.c
mnemofs_journal.c
mnemofs_lru.c
mnemofs_master.c
mnemofs_rw.c
mnemofs_util.c
mnemofs.c)
target_sources(fs PRIVATE mnemofs.c mnemofs_alloc.c mnemofs_ctz.c
mnemofs_dirent.c mnemofs_file.c mnemofs_rw.c)
endif()

View file

@ -9,44 +9,3 @@ config FS_MNEMOFS
depends on !DISABLE_MOUNTPOINT && MTD_NAND
---help---
Build the mnemofs NAND flash file system.
if FS_MNEMOFS
config MNEMOFS_EXTRA_DEBUG
bool "MNEMOFS Extra Debug Logs"
default n
depends on FS_MNEMOFS
---help---
Prints extra log information related to mnemofs.
config MNEMOFS_JOURNAL_NBLKS
int "MNEMOFS Journal Block Count"
default 20
range 4 65536
depends on FS_MNEMOFS
---help---
Number of blocks that mnemofs will use for the journal. Specifying
this will only work on formatting a NAND flash using mnemofs. If the
device is already formatted, the on-flash journal block count will
be considered instead. Two additional blocks will be allocated for
the master blocks.
config MNEMOFS_NLRU
int "MNEMOFS LRU Node Count"
default 20
range 1 255
depends on FS_MNEMOFS
---help---
Number of nodes used by mnemofs for LRU. The higher the value is,
the lesser would be the wear on device with higher RAM
consumption.
config MNEMOFS_NLRUDELTA
int "MNEMOFS LRU Delta Count"
default 20
range 1 255
depends on FS_MNEMOFS
---help---
Number of deltas used by mnemofs for LRU for every node. The higher
the value is, the lesser would be the wear on device with higher RAM
consumption.
endif # FS_MNEMOFS

View file

@ -54,15 +54,12 @@ ifeq ($(CONFIG_FS_MNEMOFS),y)
# Add the mnemofs C files to the build
CSRCS += mnemofs_blkalloc.c
CSRCS += mnemofs_ctz.c
CSRCS += mnemofs_fsobj.c
CSRCS += mnemofs_journal.c
CSRCS += mnemofs_lru.c
CSRCS += mnemofs_master.c
CSRCS += mnemofs_rw.c
CSRCS += mnemofs_util.c
CSRCS += mnemofs.c
CSRCS += mnemofs_alloc.c
CSRCS += mnemofs_ctz.c
CSRCS += mnemofs_dirent.c
CSRCS += mnemofs_file.c
CSRCS += mnemofs_rw.c
# Add the mnemofs directory to the build

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

1343
fs/mnemofs/mnemofs_alloc.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,580 +0,0 @@
/****************************************************************************
* fs/mnemofs/mnemofs_blkalloc.c
*
* SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
* Alternatively, the contents of this file may be used under the terms of
* the BSD-3-Clause license:
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024 Saurav Pal
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors may
* be used to endorse or promote products derived from this software
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/* mnemofs block allocator takes some inspiration from littlefs's block
* allocator.
*
* It has two primary jobs...provide a block and ensure wear levelling. The
* block allocator of mnemofs tries to provide a block that will more or less
* ensure wear levelling. We'll call the block allocator as BA.
*
* The block allocator starts at a random block in the device and starts a
* circular allocation from there, ie. it allocated sequentially till it
* reaches the end, at which point it cycles back to the beginning and then
* continues allocating sequentially. If a page is requested it will check if
* the page has been written to (being used). If a page is being written to
* but all the pages in a block are ready to be erased, then the block is
* erased and page is allocated. If none of these two conditions match, it
* moves on to check the next page and so on. If the block that contains the
* page is a bad block, the BA skips all the pages in the entire block.
*
* The BA can also grant a request for an entire block. If the BA is
* currently in the middle of a block, it will skip the remaining pages till
* it reaches the start of the next block. These pages won't be reflected as
* being used, and can be allocated the next time the BA cycles back to these
* pages. Even though skipped pages will be eventually utilized later anyway,
* block allocation requests are made by very few critical data structures
* in mnemofs, and they all do it in bulk, and thus skipped pages are
* minimal.
*/
/****************************************************************************
* Included Files
****************************************************************************/
#include <math.h>
#include <nuttx/kmalloc.h>
#include <stdbool.h>
#include <stdlib.h>
#include "mnemofs.h"
#include "fs_heap.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define BMAP_GET(bmap, idx, off) (((bmap)[(idx)] & (1 << (off))) != 0)
#define BMAP_SET(bmap, idx, off) ((bmap)[(idx)] |= (1 << (off)))
#define DEL_ARR_BLK(sb, blk) (MFS_BA((sb)).k_del[(blk) * sizeof(size_t)])
#define DEL_ARR_PG(sb, pg) (DEL_ARR_BLK(sb, MFS_PG2BLK((sb), (pg))))
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static inline void pg2bmap(mfs_t pg, FAR mfs_t *idx, FAR uint8_t *off);
static int is_pg_writeable(FAR struct mfs_sb_s * const sb, mfs_t pg,
FAR mfs_t *idx, FAR uint8_t *off);
static int is_blk_writeable(FAR struct mfs_sb_s * const sb,
const mfs_t blk);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: pg2bmap
*
* Description:
* Gets the bitmap location of a page. The page in the bitmap will be in
* bmap[idx] byte at (1 << off) position in the byte.
*
* Input Parameters:
* pg - Page number to check.
* idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs
* off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs
*
* Assumptions/Limitations:
* Does not check validity of the index.
*
****************************************************************************/
static inline void pg2bmap(mfs_t pg, FAR mfs_t *idx, FAR uint8_t *off)
{
/* The compiler should automatically use shift operation for division. */
*idx = pg / 8;
*off = pg % 8;
}
/****************************************************************************
* Name: is_pg_writeable
*
* Description:
* Checks if a page is writeable by checking if the page is either free, or
* it's being used but the entire block is ready for erase.
*
* Input Parameters:
* sb - Superblock instance of the device.
* pg - Page number to check.
* idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs
* off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs
*
* Returned Value:
* MFS_BLK_BAD - If the block of the page is a bad block.
* MFS_PG_USED - If the page is being used.
* MFS_BLK_ERASABLE - If page can be allocated, but block needs erase.
* MFS_PG_FREE - If the page is free.
* -ENOSYS - Not supported.
*
* Assumptions/Limitations:
* Assumes this is run in a locked environment.
*
****************************************************************************/
static int is_pg_writeable(FAR struct mfs_sb_s * const sb, mfs_t pg,
FAR mfs_t *idx, FAR uint8_t *off)
{
int blkbad_status;
/* Bad block check. */
blkbad_status = mfs_isbadblk(sb, MFS_PG2BLK(sb, pg));
if (predict_false(blkbad_status == -ENOSYS))
{
return blkbad_status;
}
if (predict_false(blkbad_status < 0) || blkbad_status == 1)
{
return MFS_BLK_BAD;
}
pg2bmap(MFS_BA(sb).c_pg, idx, off);
if (BMAP_GET(MFS_BA(sb).bmap_upgs, *idx, *off))
{
if (DEL_ARR_PG(sb, MFS_BA(sb).c_pg) == MFS_PGINBLK(sb))
{
return MFS_BLK_ERASABLE;
}
else
{
return MFS_PG_USED;
}
}
else
{
return MFS_PG_FREE;
}
}
/****************************************************************************
* Name: is_blk_writeable
*
* Description:
* Checks if an entire block is allocatable, either because none of the
* pages in it have been allocated, or because the entire block can be
* erased.
*
* Input Parameters:
* sb - Superblock instance of the device.
* pg - Page number to check.
* idx - Populated later with the index of page in MFS_BA(sb).bmap_upgs
* off - Populated later with the offset of page in MFS_BA(sb).bmap_upgs
*
* Returned Value:
* MFS_BLK_BAD - If the block is a bad block.
* MFS_BLK_USED - If the block is being used.
* MFS_BLK_ERASABLE - If block can be allocated, but block needs erase.
* MFS_BLK_FREE - If the block is free.
*
* Assumptions/Limitations:
* Assumes this is run in a locked environment.
*
****************************************************************************/
static int is_blk_writeable(FAR struct mfs_sb_s * const sb, const mfs_t blk)
{
int blkbad_status;
mfs_t i;
mfs_t pg = MFS_BLK2PG(sb, blk);
mfs_t idx;
uint8_t off;
/* Bad block check. */
blkbad_status = mfs_isbadblk(sb, blk);
if (predict_false(blkbad_status == -ENOSYS))
{
return blkbad_status;
}
if (predict_false(blkbad_status < 0) || blkbad_status == 1)
{
return MFS_BLK_BAD;
}
for (i = 0; i < MFS_PGINBLK(sb); i++)
{
pg2bmap(pg + i, &idx, &off);
if (BMAP_GET(MFS_BA(sb).bmap_upgs, idx, off))
{
if (DEL_ARR_PG(sb, MFS_BA(sb).c_pg) == MFS_PGINBLK(sb))
{
return MFS_BLK_ERASABLE;
}
else
{
return MFS_BLK_USED;
}
}
}
return MFS_BLK_FREE;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int mfs_ba_fmt(FAR struct mfs_sb_s * const sb)
{
int ret = OK;
/* We need at least 5 blocks, as one is occupied by superblock, at least
* one for the journal, 2 for journal's master blocks, and at least one for
* actual data.
*/
if (MFS_NBLKS(sb) < 5)
{
ret = -ENOSPC;
goto errout;
}
memset(&MFS_BA(sb), 0, sizeof(MFS_BA(sb)));
MFS_BA(sb).s_blk = rand() % MFS_NBLKS(sb);
if (MFS_PG2BLK(sb, MFS_BA(sb).s_blk) == sb->sb_blk)
{
MFS_BA(sb).s_blk++;
MFS_BA(sb).s_blk %= MFS_NBLKS(sb);
}
MFS_BA(sb).c_pg = MFS_BLK2PG(sb, MFS_BA(sb).s_blk);
/* MFS_BA(sb).k_del_elemsz = ((log + 7) & (-8)) / 8; */
MFS_BA(sb).k_del = fs_heap_zalloc(sizeof(size_t) * MFS_NBLKS(sb));
if (predict_false(MFS_BA(sb).k_del == NULL))
{
ret = -ENOMEM;
goto errout;
}
MFS_BA(sb).n_bmap_upgs = MFS_UPPER8(MFS_NPGS(sb));
MFS_BA(sb).bmap_upgs = fs_heap_zalloc(MFS_BA(sb).n_bmap_upgs);
if (predict_false(MFS_BA(sb).bmap_upgs == NULL))
{
ret = -ENOMEM;
goto errout_with_k_del;
}
/* TODO: Do not start from journal blocks. */
finfo("mnemofs: Block Allocator initialized, starting at page %d.\n",
MFS_BLK2PG(sb, MFS_BA(sb).s_blk));
return ret;
errout_with_k_del:
fs_heap_free(MFS_BA(sb).k_del);
errout:
return ret;
}
int mfs_ba_init(FAR struct mfs_sb_s * const sb)
{
/* TODO: Ensure journal and master node are initialized before this. */
int ret = OK;
ret = mfs_ba_fmt(sb);
if (predict_false(ret < 0))
{
goto errout;
}
/* Traverse the FS tree. */
ret = mfs_pitr_traversefs(sb, MFS_MN(sb).root_ctz, MFS_ISDIR);
if (predict_false(ret < 0))
{
goto errout_with_ba;
}
return ret;
errout_with_ba:
mfs_ba_free(sb);
errout:
return ret;
}
void mfs_ba_free(FAR struct mfs_sb_s * const sb)
{
fs_heap_free(MFS_BA(sb).k_del);
fs_heap_free(MFS_BA(sb).bmap_upgs);
finfo("Block Allocator Freed.");
}
mfs_t mfs_ba_getpg(FAR struct mfs_sb_s * const sb)
{
bool inc = true;
bool found = false;
mfs_t i = MFS_BA(sb).c_pg;
mfs_t pg = 0;
mfs_t idx;
mfs_t tpgs = MFS_NBLKS(sb) * MFS_PGINBLK(sb);
uint8_t off;
for (; i != tpgs; i++)
{
switch (is_pg_writeable(sb, MFS_BA(sb).c_pg, &idx, &off))
{
case MFS_PG_USED:
finfo("Used %d\n", MFS_BA(sb).c_pg);
break;
case MFS_PG_FREE:
finfo("Free %d\n", MFS_BA(sb).c_pg);
pg = MFS_BA(sb).c_pg;
mfs_ba_markusedpg(sb, pg);
found = true;
break;
case MFS_BLK_BAD:
finfo("Bad %d\n", MFS_BA(sb).c_pg);
/* Skip pages to next block. */
MFS_BA(sb).c_pg = MFS_BLK2PG(sb,
(MFS_PG2BLK(sb, MFS_BA(sb).c_pg) + 1) %
MFS_NBLKS(sb));
inc = false;
break;
case MFS_BLK_ERASABLE:
finfo("Erasable %d\n", MFS_BA(sb).c_pg);
pg = MFS_BA(sb).c_pg;
mfs_erase_blk(sb, MFS_PG2BLK(sb, MFS_BA(sb).c_pg));
DEL_ARR_PG(sb, MFS_BA(sb).c_pg) = 0;
mfs_ba_markusedpg(sb, pg);
found = true;
break;
case -ENOSYS:
/* TODO: Manually check for bad blocks. */
return 0;
}
if (inc)
{
MFS_BA(sb).c_pg++;
MFS_BA(sb).c_pg %= tpgs;
}
else
{
i--;
inc = true;
}
if (found)
{
break;
}
}
if (!found)
{
DEBUGASSERT(pg == 0);
finfo("No more pages found. Page: %u.", pg);
}
return pg;
}
mfs_t mfs_ba_getblk(FAR struct mfs_sb_s * const sb)
{
bool found = false;
mfs_t i = 0;
mfs_t blk;
mfs_t ret = 0;
blk = MFS_PG2BLK(sb, MFS_BA(sb).c_pg);
if (MFS_BA(sb).c_pg % MFS_PGINBLK(sb))
{
/* Skipped pages are not updated in used. */
blk++;
blk %= MFS_NBLKS(sb);
i++;
}
for (; i < MFS_NBLKS(sb); i++)
{
switch (is_blk_writeable(sb, blk))
{
case MFS_BLK_BAD:
break;
case MFS_BLK_USED:
break;
case MFS_BLK_ERASABLE:
mfs_ba_blkmarkdel(sb, blk);
mfs_ba_markusedblk(sb, blk);
found = true;
break;
case MFS_BLK_FREE:
mfs_ba_markusedblk(sb, blk);
found = true;
break;
case -ENOSYS:
/* TODO: Manually check for bad blocks. */
return 0;
}
if (found)
{
break;
}
blk++;
blk %= MFS_NBLKS(sb);
}
if (found)
{
ret = blk;
MFS_BA(sb).c_pg = MFS_BLK2PG(sb, (++blk) % MFS_NBLKS(sb));
}
finfo("Block number: %u. Found: %d.", ret, found);
return ret;
}
void mfs_ba_pgmarkdel(FAR struct mfs_sb_s * const sb, mfs_t pg)
{
DEL_ARR_PG(sb, MFS_BA(sb).c_pg)++;
}
void mfs_ba_blkmarkdel(FAR struct mfs_sb_s * const sb, mfs_t blk)
{
DEL_ARR_BLK(sb, blk) = MFS_PGINBLK(sb);
}
int mfs_ba_delmarked(FAR struct mfs_sb_s * const sb)
{
int ret = OK;
mfs_t i;
for (i = 1; i < MFS_NBLKS(sb); i++)
{
if (DEL_ARR_BLK(sb, i) == MFS_PGINBLK(sb))
{
ret = mfs_erase_blk(sb, i);
if (ret != OK)
{
return ret;
}
}
}
return ret;
}
/* Mark a page as being used. Used by master node during initial format and
*/
void mfs_ba_markusedpg(FAR struct mfs_sb_s * const sb, mfs_t pg)
{
mfs_t idx;
uint8_t off;
pg2bmap(pg, &idx, &off);
BMAP_SET(MFS_BA(sb).bmap_upgs, idx, off); /* Set as used */
}
void mfs_ba_markusedblk(FAR struct mfs_sb_s * const sb, mfs_t blk)
{
mfs_t i = 0;
mfs_t pg = MFS_BLK2PG(sb, blk);
for (i = 0; i < MFS_PGINBLK(sb); i++)
{
mfs_ba_markusedpg(sb, pg + i);
}
}
mfs_t mfs_ba_getavailpgs(FAR const struct mfs_sb_s * const sb)
{
/* TODO */
return 0;
}

View file

@ -50,613 +50,490 @@
*
****************************************************************************/
/****************************************************************************
* In mnemofs, the files and directories use the CTZ skip list data structure
* defined by littlefs. These are reverse skip lists with a specific number
* of pointers for each block. The number of pointers for a block at index
* `x` is `ctz(x) + 1`. There are no pointers if the index is 0.
*
* The pointers all point to some CTZ block other than the CTZ block they are
* part of. The `k`th pointer of a CTZ block at index `x` points to the
* CTZ block at index `x - 2^k`.
*
* For example, CTZ block at index 2 has 2 pointers, and they point to the
* block at index 1, and index 0 respectively.
*
* File/Dir Ptr
* |
* V
* +------+ +------+ +------+ +------+ +------+ +------+
* | |<--| |---| |---| |---| | | |
* | Node |<--| Node |---| Node |<--| Node |---| Node | | Node |
* | 0 |<--| 1 |<--| 2 |<--| 3 |<--| 4 |<--| 5 |
* +------+ +------+ +------+ +------+ +------+ +------+
*
* In mnemofs, each CTZ block is stored in a page on the flash. All code in
* this entire file will call CTZ blocks as blocks to honour the original
* naming, and will specify wherever it deviates from this assumption.
*
* Littlefs's design documentation lists all the benefits that this data
* structure brings to the table when it comes to storing large pieces of
* data that will be modified considerably frequently, while being in a
* Copy On Write (CoW) environment.
*
* In mnemofs, the CTZ methods only interface with the underlying R/W methods
* , journal on the lower side and on the upper side, the LRU, and ensures
* that whatever data it provides considers both the on-flash data, as well
* the journal logs.
*
* The pointers are stored such that the first pointer, which points to
* (x - 2^0), is stored at the very end of the CTZ block. The second pointer
* is stored second last, and so on.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/debug.h>
#include <fcntl.h>
#include <errno.h>
#include <nuttx/kmalloc.h>
#include <math.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <string.h>
#include <sys/endian.h>
#include "mnemofs.h"
#include "fs_heap.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MFS_CTZ_PTRSZ (sizeof(mfs_t))
/****************************************************************************
* Private Types
****************************************************************************/
#define MFS_CTZ_PTR_SIZE sizeof(mfs_t)
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static mfs_t ctz_idx_nptrs(const mfs_t idx);
static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off,
FAR mfs_t *idx, FAR mfs_t *pgoff);
static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb,
const mfs_t idx);
static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb,
struct mfs_ctz_s ctz, const mfs_t idx,
FAR char *buf);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
static int mfs_ctz_nptrs(mfs_t index);
static int mfs_ctz_read_ptr(FAR struct mfs_sb_s *sb, mfs_t page,
unsigned int pow, FAR uint8_t *scratch,
FAR mfs_t *ptrpage);
static void mfs_ctz_write_ptr(FAR struct mfs_sb_s *sb, FAR uint8_t *buffer,
unsigned int pow, mfs_t page);
static int mfs_ctz_follow(FAR struct mfs_sb_s *sb, mfs_t startidx,
mfs_t endidx, mfs_t startpage,
FAR uint8_t *scratch,
FAR mfs_t *endpage);
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ctz_idx_nptrs
* Name: mfs_ctz_nptrs
*
* Description:
* Gives the numbers of pointers that a CTZ block of given index should
* have.
* Return the number of backward pointers stored in CTZ unit index.
*
* Input Parameters:
* idx - Index of the ctz block.
* index - The CTZ unit index to inspect.
*
* Returned Value:
* The number of pointers in the CTZ block.
* The number of pointers stored in the unit is returned.
*
****************************************************************************/
static mfs_t ctz_idx_nptrs(const mfs_t idx)
static int mfs_ctz_nptrs(mfs_t index)
{
mfs_t ret;
int nptrs;
ret = (idx == 0) ? 0 : mfs_ctz(idx) + 1;
finfo("Number of pointers for %u index is %u.", idx, ret);
return ret;
if (index == 0)
{
return 0;
}
nptrs = 1;
while ((index & 1) == 0)
{
nptrs++;
index >>= 1;
}
return nptrs;
}
/****************************************************************************
* Name: ctz_off2loc
* Name: mfs_ctz_read_ptr
*
* Description:
* Converts ctz offset (which is the offset of the data stored in the ctz
* list, which is unaware of the presence of pointers) into the CTZ
* block index and the offset in that CTZ block.
* Read one backward pointer from the pointer area of a CTZ unit.
*
* Input Parameters:
* sb - Superblock instance of the device.
* off - Offset of the data stored in the CTZ list.
* idx - Index of the CTZ block, to be populated.
* pgoff - Offset inside the CTZ block, to be populated.
*
****************************************************************************/
static void ctz_off2loc(FAR const struct mfs_sb_s * const sb, mfs_t off,
FAR mfs_t *idx, FAR mfs_t *pgoff)
{
const mfs_t wb = sizeof(mfs_t);
const mfs_t den = MFS_PGSZ(sb) - 2 * wb;
if (off < den)
{
*idx = 0;
*pgoff = off;
return;
}
if (idx != NULL)
{
*idx = (off - wb * (__builtin_popcount((off / den) - 1) + 2)) / den;
}
if (pgoff != NULL)
{
*pgoff = off - den * (*idx) - wb * __builtin_popcount(*idx)
- (ctz_idx_nptrs(*idx) * wb);
}
finfo("Offset %u. Calculated index %u and page offset %u.", off, *idx,
*pgoff);
}
/****************************************************************************
* Name: ctz_blkdatasz
*
* Description:
* The size of data in B that can be fit inside a CTZ block at index `idx`.
*
* Input Parameters:
* sb - Superblock instance of the device.
* idx - Index of the ctz block.
* sb - The mounted file system instance.
* page - The CTZ unit page to read.
* pow - The pointer slot number to decode.
* scratch - A page-sized scratch buffer.
* ptrpage - The location to receive the decoded pointer target page.
*
* Returned Value:
* The size of data in the CTZ block.
* Zero (OK) is returned on success. A negated errno value is returned if
* the inputs are invalid, the page cannot be read, or the decoded pointer
* is outside the valid page range.
*
****************************************************************************/
static mfs_t ctz_blkdatasz(FAR const struct mfs_sb_s * const sb,
const mfs_t idx)
static int mfs_ctz_read_ptr(FAR struct mfs_sb_s *sb, mfs_t page,
unsigned int pow, FAR uint8_t *scratch,
FAR mfs_t *ptrpage)
{
mfs_t ret;
size_t offset;
ssize_t nread;
mfs_t raw;
ret = MFS_PGSZ(sb) - (ctz_idx_nptrs(idx) * MFS_LOGPGSZ(sb));
finfo("Block data size for index %u is %u.", idx, ret);
return ret;
if (sb == NULL || scratch == NULL || ptrpage == NULL)
{
ferr("invalid args\n");
return -EINVAL;
}
if (page == MFS_LOCATION_INVALID || page >= MFS_PAGE_COUNT(sb))
{
ferr("invalid page\n");
return -EINVAL;
}
if ((pow + 1) * MFS_CTZ_PTR_SIZE > MFS_PAGE_SIZE(sb))
{
ferr("invalid pointer slot\n");
return -EINVAL;
}
nread = mfs_read_page(sb, page, scratch);
if (nread < 0)
{
ferr("mfs_read_page failed: %zd\n", nread);
return nread;
}
if (nread != 1)
{
ferr("short read: %zd\n", nread);
return -EIO;
}
offset = MFS_PAGE_SIZE(sb) - ((pow + 1) * MFS_CTZ_PTR_SIZE);
memcpy(&raw, scratch + offset, sizeof(raw));
* ptrpage = le32toh(raw);
if (*ptrpage == MFS_LOCATION_INVALID || *ptrpage >= MFS_PAGE_COUNT(sb))
{
ferr("invalid pointer target\n");
return -EIO;
}
return OK;
}
/****************************************************************************
* Name: ctz_copyidxptrs
* Name: mfs_ctz_write_ptr
*
* Description:
* This is used for cases when you want to expand a CTZ list from any point
* in the list. If we want to expand the CTZ list from a particular index,
* say `start_idx`, while keeping all indexes before it untouched, we
* would need to first allocate new blocks on the flash, and then copy
* the pointers to the location.
*
* Usage of this function is, the caller needs to first allocate a CTZ
* block (a page on flash), allocate buffer which is the size of a CTZ
* block (a page on flash), and use this method to copy the pointers to the
* buffer, then write the data to the flash.
* Encode one backward pointer into the pointer area of a CTZ unit buffer.
*
* Input Parameters:
* sb - Superblock instance of the device.
* ctz - CTZ list to use as a reference.
* idx - Index of the block who's supposed pointers are to be copied.
* buf - Buffer representing the entire CTZ block where pointers are
* copied to.
* sb - The mounted file system instance.
* buffer - The page-sized CTZ unit buffer to modify.
* pow - The pointer slot number to write.
* page - The target page to encode.
*
* Assumptions/Limitations:
* This assumes `idx` is not more than `ctz->idx_e + 1`.
* Returned Value:
* None.
*
****************************************************************************/
static void ctz_copyidxptrs(FAR const struct mfs_sb_s * const sb,
struct mfs_ctz_s ctz, const mfs_t idx,
FAR char *buf)
static void mfs_ctz_write_ptr(FAR struct mfs_sb_s *sb, FAR uint8_t *buffer,
unsigned int pow, mfs_t page)
{
mfs_t i;
mfs_t n_ptrs;
mfs_t prev_pg;
mfs_t prev_idx;
size_t offset;
mfs_t raw;
if (idx == 0)
DEBUGASSERT(sb != NULL && buffer != NULL);
DEBUGASSERT((pow + 1) * MFS_CTZ_PTR_SIZE <= MFS_PAGE_SIZE(sb));
offset = MFS_PAGE_SIZE(sb) - ((pow + 1) * MFS_CTZ_PTR_SIZE);
raw = htole32(page);
memcpy(buffer + offset, &raw, sizeof(raw));
}
/****************************************************************************
* Name: mfs_ctz_follow
*
* Description:
* Walk backward through a CTZ chain from startidx/startpage until endidx
* is reached.
*
* Input Parameters:
* sb - The mounted file system instance.
* startidx - The index of the known CTZ unit.
* endidx - The target earlier index to reach.
* startpage - The page of the known CTZ unit.
* scratch - A page-sized scratch buffer.
* endpage - The location to receive the page at endidx.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* invalid input or if any pointer read fails.
*
****************************************************************************/
static int mfs_ctz_follow(FAR struct mfs_sb_s *sb, mfs_t startidx,
mfs_t endidx, mfs_t startpage,
FAR uint8_t *scratch,
FAR mfs_t *endpage)
{
mfs_t diff;
mfs_t page;
mfs_t idx;
unsigned int pow;
unsigned int maxpow;
int ret;
if (sb == NULL || scratch == NULL || endpage == NULL)
{
/* No pointers for first block. */
return;
ferr("invalid args\n");
return -EINVAL;
}
n_ptrs = ctz_idx_nptrs(idx);
if (idx != ctz.idx_e + 1)
if (startpage == MFS_LOCATION_INVALID || startpage >= MFS_PAGE_COUNT(sb) ||
startidx < endidx)
{
/* We travel to the second last "known" CTZ block. */
ctz.pg_e = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, idx - 1);
ctz.idx_e = idx - 1;
ferr("invalid traversal request\n");
return -EINVAL;
}
buf += MFS_PGSZ(sb); /* Go to buf + pg_sz */
DEBUGASSERT(idx == ctz.idx_e + 1);
finfo("Copying %u pointers for CTZ (%u, %u) at index %u.", n_ptrs,
ctz.idx_e, ctz.pg_e, idx);
for (i = 0; i < n_ptrs; i++)
page = startpage;
idx = startidx;
while (idx > endidx)
{
if (predict_false(i == 0))
diff = idx - endidx;
pow = (sizeof(diff) * 8) - 1 - __builtin_clz(diff);
maxpow = __builtin_ctz(idx);
if (pow > maxpow)
{
prev_idx = ctz.idx_e;
prev_pg = ctz.pg_e;
}
else
{
prev_pg = mfs_ctz_travel(sb, prev_idx, prev_pg, prev_idx - 1);
prev_idx--;
pow = maxpow;
}
ctz.idx_e = prev_idx;
DEBUGASSERT(pow < (unsigned int)mfs_ctz_nptrs(idx));
ret = mfs_ctz_read_ptr(sb, page, pow, scratch, &page);
if (ret < 0)
{
ferr("mfs_ctz_read_ptr failed: %d\n", ret);
return ret;
}
/* Do buf + pg_sz - (idx * sizeof(mfs_t)) iteratively. */
buf -= MFS_CTZ_PTRSZ;
mfs_ser_mfs(prev_pg, buf);
finfo("Copied %u page number to %uth pointer.", prev_pg, i);
idx -= (mfs_t)1 << pow;
}
* endpage = page;
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int mfs_ctz_rdfromoff(FAR const struct mfs_sb_s * const sb,
const struct mfs_ctz_s ctz, mfs_t data_off,
mfs_t len, FAR char * buf)
/****************************************************************************
* Name: mfs_ctz_traverse
*
* Description:
* Traverse a CTZ chain from one known unit index/page to an earlier
* index.
*
* Input Parameters:
* sb - The mounted file system instance.
* startidx - The index of the known CTZ unit.
* endidx - The earlier index to reach.
* startpage - The page of the known CTZ unit.
* endpage - The location to receive the resolved page.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* invalid input, allocation failure, or traversal failure.
*
****************************************************************************/
int mfs_ctz_traverse(FAR struct mfs_sb_s *sb, mfs_t startidx, mfs_t endidx,
mfs_t startpage, FAR mfs_t *endpage)
{
int ret = OK;
mfs_t i;
mfs_t cur_pg;
mfs_t cur_idx;
mfs_t cur_pgoff;
mfs_t end_idx;
mfs_t end_pgoff;
mfs_t pg_rd_sz;
FAR uint8_t *scratch;
int ret;
finfo("Reading (%u, %u) CTZ from %u offset for %u bytes.", ctz.idx_e,
ctz.pg_e, data_off, len);
if (ctz.idx_e == 0 && ctz.pg_e == 0)
if (sb == NULL || endpage == NULL || startpage == MFS_LOCATION_INVALID ||
startpage >= MFS_PAGE_COUNT(sb) || startidx < endidx)
{
goto errout;
ferr("invalid args\n");
return -EINVAL;
}
ctz_off2loc(sb, data_off + len, &cur_idx, &cur_pgoff);
ctz_off2loc(sb, data_off, &end_idx, &end_pgoff);
DEBUGASSERT(ctz.idx_e < cur_idx); /* TODO: Need to consider this. For now, there is a temporary fix in read(). */
if (ctz.idx_e < end_idx)
scratch = kmm_malloc(MFS_PAGE_SIZE(sb));
if (scratch == NULL)
{
goto errout;
ferr("kmm_malloc failed\n");
return -ENOMEM;
}
cur_pg = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, cur_idx);
if (predict_false(cur_pg == 0))
ret = mfs_ctz_follow(sb, startidx, endidx, startpage, scratch, endpage);
if (ret < 0)
{
goto errout;
ferr("mfs_ctz_follow failed: %d\n", ret);
}
/* O(n) read by reading in reverse. */
finfo("Started reading. Current Idx: %u, End Idx: %u.", cur_idx, end_idx);
if (cur_idx != end_idx)
{
for (i = cur_idx; i >= end_idx; i--)
{
finfo("Current index %u, Current Page %u.", i, cur_pg);
if (predict_false(i == cur_idx))
{
pg_rd_sz = cur_pgoff;
ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg,
0);
cur_pgoff = 0;
}
else if (predict_false(i == end_idx))
{
pg_rd_sz = ctz_blkdatasz(sb, i) - end_pgoff;
ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg,
end_pgoff);
}
else
{
pg_rd_sz = ctz_blkdatasz(sb, i);
ret = mfs_read_page(sb, buf - pg_rd_sz, pg_rd_sz, cur_pg,
0);
}
if (predict_false(ret == 0))
{
ret = -EINVAL;
goto errout;
}
buf -= pg_rd_sz;
}
cur_pg = mfs_ctz_travel(sb, cur_idx, cur_pg, cur_idx - 1);
if (predict_false(cur_pg == 0))
{
ret = -EINVAL;
goto errout;
}
}
else
{
ret = mfs_read_page(sb, buf, len, cur_pg, end_pgoff);
if (predict_false(ret < 0))
{
goto errout;
}
ret = OK;
}
finfo("Reading finished.");
errout:
kmm_free(scratch);
return ret;
}
int mfs_ctz_wrtnode(FAR struct mfs_sb_s * const sb,
FAR const struct mfs_node_s * const node,
FAR struct mfs_ctz_s *new_loc)
/****************************************************************************
* Name: mfs_ctz_unit_data_area
*
* Description:
* Return the number of payload bytes available in CTZ unit index after
* its backward pointers are reserved.
*
* Input Parameters:
* sb - The mounted file system instance.
* index - The CTZ unit index to inspect.
*
* Returned Value:
* The number of payload bytes available in the unit is returned.
*
****************************************************************************/
mfs_t mfs_ctz_unit_data_area(FAR struct mfs_sb_s *sb, mfs_t index)
{
int ret = OK;
bool written = false;
mfs_t prev;
mfs_t rem_sz;
mfs_t new_pg;
mfs_t cur_pg;
mfs_t cur_idx;
mfs_t cur_pgoff;
mfs_t lower;
mfs_t upper;
mfs_t upper_og;
mfs_t lower_upd;
mfs_t upper_upd;
mfs_t del_bytes;
FAR char *buf = NULL;
FAR char *tmp = NULL;
struct mfs_ctz_s ctz;
FAR struct mfs_delta_s *delta;
finfo("Write LRU node %p at depth %u.", node, node->depth);
/* Traverse common CTZ blocks. */
ctz_off2loc(sb, node->range_min, &cur_idx, &cur_pgoff);
ctz = node->path[node->depth - 1].ctz;
cur_pg = mfs_ctz_travel(sb, ctz.idx_e, ctz.pg_e, cur_idx);
/* So, till cur_idx - 1, the CTZ blocks are common. */
buf = fs_heap_zalloc(MFS_PGSZ(sb));
if (predict_false(buf == NULL))
{
ret = -ENOMEM;
goto errout;
}
/* Initially, there might be some offset in cur_idx CTZ blocks that is
* unmodified as well.
*/
finfo("Initial read.");
tmp = buf;
mfs_read_page(sb, tmp, cur_pgoff, cur_pg, 0);
tmp += cur_pgoff;
/* Modifications. */
prev = 0;
rem_sz = node->sz;
lower = node->range_min;
del_bytes = 0;
/* [lower, upper) range. Two pointer approach. Window gets narrower
* for every delete falling inside it.
*/
while (rem_sz > 0)
{
upper = MIN(prev + lower + ctz_blkdatasz(sb, cur_idx), rem_sz);
upper_og = upper;
finfo("Remaining Size %" PRIu32 ". Lower %" PRIu32 ", Upper %" PRIu32
", Current Offset %zd.", rem_sz, lower, upper, tmp - buf);
/* Retrieving original data. */
ret = mfs_ctz_rdfromoff(sb, ctz, lower + del_bytes, upper - lower,
tmp);
if (predict_false(ret < 0))
{
goto errout_with_buf;
}
list_for_every_entry(&node->delta, delta, struct mfs_delta_s, list)
{
finfo("Checking delta %p in node %p. Offset %" PRIu32 ", bytes %"
PRIu32, delta, node, delta->off, delta->n_b);
lower_upd = MAX(lower, delta->off);
upper_upd = MIN(upper, delta->off + delta->n_b);
if (lower_upd >= upper_upd)
{
/* Skip this delta. */
continue;
}
if (delta->upd == NULL)
{
finfo("Node type: Delete");
/* Delete */
del_bytes += upper_upd - lower_upd;
memmove(tmp + (lower_upd - lower), tmp + (upper_upd - lower),
upper - upper_upd);
upper -= upper_upd;
}
else
{
finfo("Node type: Update");
/* Update */
memcpy(tmp + (lower_upd - lower),
delta->upd + (lower_upd - delta->off),
upper_upd - lower_upd);
}
}
/* rem_sz check for final write. */
if (upper == upper_og || rem_sz == upper - lower)
{
prev = 0;
/* Time to write a page for new CTZ list. */
new_pg = mfs_ba_getpg(sb);
if (predict_false(new_pg == 0))
{
ret = -ENOSPC;
goto errout_with_buf;
}
ctz_copyidxptrs(sb, ctz, cur_idx, buf);
ret = mfs_write_page(sb, buf, MFS_PGSZ(sb), new_pg, 0);
if (predict_false(ret == 0))
{
ret = -EINVAL;
goto errout_with_buf;
}
memset(buf, 0, MFS_PGSZ(sb));
tmp = buf;
ctz.idx_e = cur_idx;
ctz.pg_e = new_pg;
cur_idx++;
written = true;
finfo("Written data to page %" PRIu32, new_pg);
}
else
{
tmp += upper - lower;
written = false;
}
prev = upper - lower;
rem_sz -= upper - lower;
lower = upper;
}
DEBUGASSERT(written);
/* TODO: Need to verify for cases where the delete extends outside, etc. */
/* Write log. Assumes journal has enough space due to the limit. */
finfo("Writing log.");
*new_loc = ctz;
ret = mfs_jrnl_wrlog(sb, node, ctz, node->sz);
if (predict_false(ret < 0))
{
goto errout_with_buf;
}
errout_with_buf:
fs_heap_free(buf);
errout:
return ret;
return (mfs_t)MFS_PAGE_SIZE(sb) - ((mfs_t)mfs_ctz_nptrs(index) * 4);
}
mfs_t mfs_ctz_travel(FAR const struct mfs_sb_s * const sb,
mfs_t idx_src, mfs_t pg_src, mfs_t idx_dest)
/****************************************************************************
* Name: mfs_ctz_index_from_off
*
* Description:
* Convert a logical file offset into the CTZ unit index and byte offset
* inside that unit.
*
* Input Parameters:
* sb - The mounted file system instance.
* offset - The logical file offset to translate.
* index - The location to receive the CTZ unit index.
* pageoff - The location to receive the byte offset within that unit.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* invalid input or overflow.
*
****************************************************************************/
int mfs_ctz_index_from_off(FAR struct mfs_sb_s *sb, mfs_t offset,
FAR mfs_t *index,
FAR mfs_t *pageoff)
{
char buf[4];
mfs_t pg;
mfs_t idx;
mfs_t pow;
mfs_t diff;
mfs_t max_pow;
mfs_t area;
/* Rising phase. */
max_pow = (sizeof(mfs_t) * 8) - mfs_clz(idx_src ^ idx_dest);
idx = idx_src;
pow = 1;
pg = pg_src;
for (pow = mfs_ctz(idx); pow < max_pow - 1; pow = mfs_ctz(idx))
if (sb == NULL || index == NULL || pageoff == NULL)
{
mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow));
mfs_deser_mfs(buf, &pg);
idx -= (1 << pow);
ferr("invalid args\n");
return -EINVAL;
}
if (pg == 0)
idx = 0;
while (true)
{
area = mfs_ctz_unit_data_area(sb, idx);
if (area == 0)
{
return 0;
ferr("zero data area\n");
return -EINVAL;
}
}
if (idx == idx_dest)
{
return pg;
}
/* Falling phase. */
diff = idx - idx_dest;
for (pow = mfs_set_msb(diff); diff != 0; pow = mfs_set_msb(diff))
{
mfs_read_page(sb, buf, 4, pg, MFS_PGSZ(sb) - (4 * pow));
mfs_deser_mfs(buf, &pg);
idx -= (1 << pow);
diff -= (1 << pow);
if (pg == 0)
if (offset < area)
{
return 0;
* index = idx;
* pageoff = offset;
return OK;
}
offset -= area;
if (idx == UINT32_MAX)
{
ferr("offset overflow\n");
return -EOVERFLOW;
}
idx++;
}
finfo("Travel from index %" PRIu32 " at page %" PRIu32 " to index %" PRIu32
" at page %" PRIu32 ".", idx_src, pg_src, idx_dest, pg);
return pg;
}
/****************************************************************************
* Name: mfs_ctz_fill_next_ptrs
*
* Description:
* Populate the pointer area in a page-sized buffer as though the buffer
* were the CTZ unit immediately after the known unit at `page`/`index`.
*
* For every pointer required by unit `index + 1`, this walks backward from
* the known unit `index` to the target `(index + 1) - 2^pow`.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The location of the known CTZ unit at `index`.
* index - The index of the known CTZ unit.
* buffer - Caller-supplied page-sized buffer to receive the speculative
* pointer bytes.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned on
* failure.
*
****************************************************************************/
int mfs_ctz_fill_next_ptrs(FAR struct mfs_sb_s *sb, mfs_t page,
mfs_t index, FAR uint8_t *buffer)
{
FAR uint8_t *scratch;
mfs_t targetidx;
mfs_t targetpage;
mfs_t nextidx;
unsigned int pow;
unsigned int nptrs;
int ret;
if (sb == NULL || buffer == NULL)
{
ferr("invalid args\n");
return -EINVAL;
}
if (page == MFS_LOCATION_INVALID || page >= MFS_PAGE_COUNT(sb))
{
ferr("invalid page\n");
return -EINVAL;
}
if (index == UINT32_MAX)
{
ferr("index overflow\n");
return -EOVERFLOW;
}
scratch = kmm_malloc(MFS_PAGE_SIZE(sb));
if (scratch == NULL)
{
ferr("kmm_malloc failed\n");
return -ENOMEM;
}
nextidx = index + 1;
nptrs = mfs_ctz_nptrs(nextidx);
for (pow = 0; pow < nptrs; pow++)
{
targetidx = index - (((mfs_t)1 << pow) - 1);
ret = mfs_ctz_follow(sb, index, targetidx, page, scratch, &targetpage);
if (ret < 0)
{
ferr("mfs_ctz_follow failed: %d\n", ret);
goto errout;
}
mfs_ctz_write_ptr(sb, buffer, pow, targetpage);
}
kmm_free(scratch);
return OK;
errout:
kmm_free(scratch);
return ret;
}
/****************************************************************************
* Name: mfs_page_to_block
*
* Description:
* Convert a page number into the block that contains it.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page number to translate.
*
* Returned Value:
* The containing block number is returned on success.
* MFS_LOCATION_INVALID is returned if the inputs are invalid.
*
****************************************************************************/
mfs_t mfs_page_to_block(FAR const struct mfs_sb_s *sb, mfs_t page)
{
if (sb == NULL || page == MFS_LOCATION_INVALID ||
page >= MFS_PAGE_COUNT(sb))
{
return MFS_LOCATION_INVALID;
}
return page / MFS_PAGES_PER_BLOCK(sb);
}

3249
fs/mnemofs/mnemofs_dirent.c Normal file

File diff suppressed because it is too large Load diff

1732
fs/mnemofs/mnemofs_file.c Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,887 +0,0 @@
/****************************************************************************
* fs/mnemofs/mnemofs_journal.c
*
* SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
* Alternatively, the contents of this file may be used under the terms of
* the BSD-3-Clause license:
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024 Saurav Pal
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors may
* be used to endorse or promote products derived from this software
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* In mnemofs, the journal stores the path, depth and the new location of the
* CTZ file called logs, and also the location of the master block. The first
* n blocks of the journal store the logs, while the last two blocks contain
* master nodes, and the blocks are called as master blocks. The two master
* blocks are identical copies for backup.
*
* Due to LRU, and the structure of mnemofs, the first n blocks of the
* journal get filled up much faster than the master blocks, and move more.
* There will be certain point where the entire journal (the n+2 blocks)
* move, but mostly, its the first n blocks that move.
*
* The first block starts with an 8 byte magic sequence, a 2 bytes long
* number denoting number of blocks in the journal, and then follows up
* with an array containing the block numbers of all blocks in the journal
* including the first block. Then the logs start.
*
* The logs take up size in multiples of pages. There might be unitilzed
* space at the end of a log.
*
* All logs are followed by a byte-long hash of the log.
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <endian.h>
#include <nuttx/kmalloc.h>
#include <nuttx/list.h>
#include <sys/param.h>
#include "mnemofs.h"
#include "fs_heap.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define MFS_JRNL_SUFFIXSZ (8 + 2) /* 8 byte magic sequence + no. of blks */
#define MFS_LOGSZ(depth) (sizeof(mfs_t) * 2 + sizeof(struct mfs_ctz_s) + \
sizeof(struct mfs_path_s) * depth + \
sizeof(struct timespec) * 3 + sizeof(uint16_t))
/****************************************************************************
* Private Types
****************************************************************************/
/* NOTE: Even if higher level functions use path as struct mfs_path_s,
* journal only uses struct mfs_ctz_s to avoid problems during write and
* read of logs. The offsets in struct mfs_path_s will be applied by the
* search methods of higher functions.
*/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
FAR static const char *deser_log(FAR const char * const in,
FAR struct mfs_jrnl_log_s * const x);
FAR static char *ser_log(FAR const struct mfs_jrnl_log_s * const x,
FAR char * const out);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mfs_jrnl_rdlog
*
* Description:
* Read a log to the journal from given location, and update location to
* point to next log location.
*
* Input Parameters:
* sb - Superblock instance of the device.
* blkidx - Journal Block Index of the current block.
* pg_in_blk - Page offset in the block.
* log - To populate with the log.
*
* Returned Value:
* 0 - OK
* < 0 - Error
*
* Assumptions/Limitations:
* This function does NOT care about start of the journal, or even, if
* the initial requested area is inside the journal. It will malfunction
* if not used properly. Usually this is used in an iterative manner, and
* hence the first time blkidx and pg_in_blk are initialized, they should
* be derived from the values in MFS_JRNL(sb) respectively.
*
* This updates the blkidx and pg_in_blk to point to the next log, and
* returns an -ENOSPC when end of journal is reached in traversal.
*
* Free the log after use.
*
****************************************************************************/
int mfs_jrnl_rdlog(FAR const struct mfs_sb_s *const sb,
FAR mfs_t *blkidx, FAR mfs_t *pg_in_blk,
FAR struct mfs_jrnl_log_s *log)
{
int ret = OK;
char tmp[4];
mfs_t log_sz;
mfs_t jrnl_pg;
mfs_t jrnl_blk;
FAR char *buf = NULL;
DEBUGASSERT(*pg_in_blk % MFS_PGSZ(sb) == 0);
jrnl_blk = mfs_jrnl_blkidx2blk(sb, *blkidx);
jrnl_pg = MFS_BLK2PG(sb, jrnl_blk) + *pg_in_blk;
/* First 4 bytes contain the size of the entire log. */
ret = mfs_read_page(sb, tmp, 4, jrnl_pg, 0);
if (predict_false(ret < 0))
{
goto errout;
}
mfs_deser_mfs(tmp, &log_sz);
if (log_sz == 0)
{
ret = -ENOSPC;
goto errout;
}
buf = fs_heap_zalloc(log_sz);
if (predict_false(buf == NULL))
{
ret = -ENOMEM;
goto errout;
}
ret = mfs_read_page(sb, buf, log_sz, jrnl_pg, 4);
if (predict_false(ret < 0))
{
goto errout_with_buf;
}
ret = OK;
if (predict_false(deser_log(buf, log) == NULL))
{
ret = -ENOMEM;
goto errout_with_buf;
}
(*pg_in_blk)++;
if (*pg_in_blk >= MFS_PGINBLK(sb))
{
*pg_in_blk = 0;
(*blkidx)++;
}
errout_with_buf:
fs_heap_free(buf);
errout:
return ret;
}
/****************************************************************************
* Name: ser_log
*
* Description:
* Serialize a log.
*
* Input Parameters:
* n - Log to serialize
* out - Output array where to serialize.
*
* Returned Value:
* Pointer to byte after the end of serialized value.
*
* Assumptions/Limitations:
* This assumes the out buffer has enough space to hold the inline path.
*
* This doesn't require the hash to be pre-calculated.
*
****************************************************************************/
FAR static char *ser_log(FAR const struct mfs_jrnl_log_s * const x,
FAR char * const out)
{
FAR char *o = out;
mfs_t i;
o = mfs_ser_mfs(x->depth, o);
o = mfs_ser_mfs(x->sz_new, o);
o = mfs_ser_ctz(&x->loc_new, o);
o = mfs_ser_timespec(&x->st_mtim_new, o);
o = mfs_ser_timespec(&x->st_atim_new, o);
o = mfs_ser_timespec(&x->st_ctim_new, o);
for (i = 0; i < x->depth; i++)
{
o = mfs_ser_path(&x->path[i], o);
}
o = mfs_ser_16(mfs_hash(out, o - out), o);
return o;
}
/****************************************************************************
* Name: deser_log
*
* Description:
* Deserialize a log.
*
* Input Parameters:
* in - Input array from where to deserialize.
* n - Log to deserialize
*
* Returned Value:
* NULL - Error.
* Pointer to byte after the end of serialized value.
*
* Assumptions/Limitations:
* This allocates space for the path, and the log should freed after use.
*
****************************************************************************/
FAR static const char *deser_log(FAR const char * const in,
FAR struct mfs_jrnl_log_s * const x)
{
FAR const char *i = in;
mfs_t k;
i = mfs_deser_mfs(i, &x->depth);
i = mfs_deser_mfs(i, &x->sz_new);
i = mfs_deser_ctz(i, &x->loc_new);
i = mfs_deser_timespec(i, &x->st_mtim_new);
i = mfs_deser_timespec(i, &x->st_atim_new);
i = mfs_deser_timespec(i, &x->st_ctim_new);
/* Allocates path. Deallocate using mfs_jrnl_log_free. */
x->path = fs_heap_zalloc(sizeof(struct mfs_jrnl_log_s) * x->depth);
if (predict_false(x->path == NULL))
{
return NULL;
}
for (k = 0; k < x->depth; k++)
{
i = mfs_deser_path(i, &x->path[k]);
}
i = mfs_deser_16(i, &x->hash);
return i;
}
/****************************************************************************
* Name: mfs_jrnl_log_free
*
* Description:
* Free the log.
*
* Input Parameters:
* log - Log
*
* Assumptions/Limitations:
* This allocates space for the path, and the log should freed after use.
*
****************************************************************************/
void mfs_jrnl_log_free(FAR const struct mfs_jrnl_log_s * const log)
{
fs_heap_free(log->path);
}
/****************************************************************************
* Public Functions
****************************************************************************/
int mfs_jrnl_init(FAR struct mfs_sb_s * const sb, mfs_t blk)
{
char buftmp[2];
int ret = OK;
mfs_t sz;
mfs_t blkidx;
mfs_t pg_in_blk;
struct mfs_jrnl_log_s log;
/* Magic sequence is already used to find the block, so not required. */
mfs_read_page(sb, buftmp, 2, MFS_BLK2PG(sb, blk), 8);
mfs_deser_16(buftmp, &MFS_JRNL(sb).n_blks);
if (MFS_JRNL(sb).n_blks == 0)
{
ret = -EINVAL;
goto errout;
}
sz = MFS_JRNL_SUFFIXSZ + ((CONFIG_MNEMOFS_JOURNAL_NBLKS + 2) * 4);
MFS_JRNL(sb).jrnl_start = blk;
MFS_JRNL(sb).log_cpg = (sz + (MFS_PGSZ(sb) - 1)) / MFS_PGSZ(sb);
MFS_JRNL(sb).log_spg = MFS_JRNL(sb).log_cpg;
MFS_JRNL(sb).log_cblkidx = 0;
MFS_JRNL(sb).log_sblkidx = MFS_JRNL(sb).log_cblkidx;
MFS_JRNL(sb).jrnlarr_pg = MFS_BLK2PG(sb, blk); /* Assuming pgsz > 10 */
MFS_JRNL(sb).jrnlarr_pgoff = MFS_JRNL_SUFFIXSZ;
/* Number of logs */
MFS_JRNL(sb).n_logs = 0;
blkidx = MFS_JRNL(sb).log_sblkidx;
pg_in_blk = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb);
while (true)
{
ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log);
if (predict_false(ret < 0 && ret != -ENOSPC))
{
goto errout;
}
else if (ret == -ENOSPC)
{
ret = OK;
break;
}
/* Assumes checking the depth is enough to check if it's empty, as
* theoretically there are no blocks with depth 0, as root has a
* depth of 1.
*/
if (log.depth == 0)
{
DEBUGASSERT(log.path == NULL);
break;
}
MFS_JRNL(sb).n_logs++;
mfs_jrnl_log_free(&log);
}
/* Master node */
MFS_JRNL(sb).mblk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks);
MFS_JRNL(sb).mblk2 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks + 1);
/* TODO: Read all pages in master blocks to find the last master node
* update.
*/
errout:
return ret;
}
int mfs_jrnl_fmt(FAR struct mfs_sb_s * const sb, FAR mfs_t *blk1,
FAR mfs_t *blk2, FAR mfs_t *jrnl_blk)
{
int i;
int ret = OK;
mfs_t sz;
mfs_t pg;
mfs_t blk;
mfs_t alloc_blk;
FAR char *tmp;
FAR char *buf = NULL;
/* TODO: Replace jrnl_blk with MFS_JRNL(sb).jrnl_start if possible. */
/* Write magic sequence, size of jrnlarr, and then the jrnlarr. */
sz = MFS_JRNL_SUFFIXSZ + ((CONFIG_MNEMOFS_JOURNAL_NBLKS + 2) * 4);
buf = fs_heap_zalloc(sz);
if (predict_false(buf == NULL))
{
ret = -ENOMEM;
goto errout;
}
if (*blk1 == 0 && *blk2 == 0)
{
*blk1 = mfs_ba_getblk(sb);
if (predict_false(blk1 == 0))
{
ret = -ENOSPC;
goto errout_with_buf;
}
finfo("Allocated Master Block 1: %d.", *blk1);
*blk2 = mfs_ba_getblk(sb);
if (predict_false(blk2 == 0))
{
ret = -ENOSPC;
goto errout_with_buf;
}
finfo("Allocated Master Block 1: %d.", *blk2);
finfo("New locations for Master Blocks %d & %d.", *blk1, *blk2);
}
tmp = buf;
tmp = mfs_ser_str(MFS_JRNL_MAGIC, 8, tmp);
tmp = mfs_ser_16(CONFIG_MNEMOFS_JOURNAL_NBLKS, tmp);
for (i = 0; i < CONFIG_MNEMOFS_JOURNAL_NBLKS; i++)
{
alloc_blk = mfs_ba_getblk(sb);
tmp = mfs_ser_mfs(alloc_blk, tmp);
if (predict_false(i == 0))
{
blk = alloc_blk;
*jrnl_blk = alloc_blk;
MFS_JRNL(sb).jrnl_start = alloc_blk;
}
finfo("Allocated Journal Block %d at Block %d.", i, alloc_blk);
}
tmp = mfs_ser_mfs(*blk1, tmp);
tmp = mfs_ser_mfs(*blk2, tmp);
finfo("All Journal Blocks allocated.");
pg = MFS_BLK2PG(sb, blk);
ret = mfs_write_page(sb, buf, sz, pg, 0); /* Assuming array fits in a
* single page.
*/
if (predict_false(ret < 0))
{
goto errout_with_buf;
}
ret = OK; /* We reach here, we OK. */
finfo("Written magic sequence, size and journal array into the journal.");
MFS_JRNL(sb).n_logs = 0;
MFS_JRNL(sb).n_blks = CONFIG_MNEMOFS_JOURNAL_NBLKS;
MFS_JRNL(sb).log_cpg = pg + 1; /* Assumes 1 page for jrnl_arr. */
MFS_JRNL(sb).log_cblkidx = 0;
MFS_JRNL(sb).log_spg = MFS_JRNL(sb).log_cpg;
MFS_JRNL(sb).log_sblkidx = MFS_JRNL(sb).log_cblkidx;
MFS_JRNL(sb).jrnlarr_pg = MFS_BLK2PG(sb, blk);
MFS_JRNL(sb).jrnlarr_pgoff = MFS_JRNL_SUFFIXSZ;
MFS_JRNL(sb).mblk1 = *blk1;
MFS_JRNL(sb).mblk2 = *blk2;
errout_with_buf:
fs_heap_free(buf);
errout:
return ret;
}
void mfs_jrnl_free(FAR struct mfs_sb_s * const sb)
{
if (!mfs_jrnl_isempty(sb) &&
MFS_JRNL(sb).log_cblkidx >= MFS_JRNL_LIM(sb))
{
mfs_jrnl_flush(sb);
}
finfo("Journal Freed.");
}
mfs_t mfs_jrnl_blkidx2blk(FAR const struct mfs_sb_s * const sb,
const mfs_t blk_idx)
{
int ret = OK;
mfs_t pg;
mfs_t idx;
mfs_t blk;
mfs_t pgoff;
char buf[4];
pg = MFS_JRNL(sb).jrnlarr_pg;
pgoff = MFS_JRNL(sb).jrnlarr_pgoff;
blk = MFS_PG2BLK(sb, pg);
pgoff += blk_idx * 4;
if (pgoff > MFS_PGSZ(sb))
{
pg += pgoff / MFS_PGSZ(sb);
pgoff %= MFS_PGSZ(sb);
}
/* No pg overflow. The blocks have to be big enough. */
DEBUGASSERT(pg < (MFS_BLK2PG(sb, blk) + MFS_PGINBLK(sb)));
ret = mfs_read_page(sb, buf, 4, pg, pgoff);
if (predict_false(ret < 0))
{
return 0;
}
mfs_deser_mfs(buf, &idx);
/* FUTURE TODO: Make it such that the entire jrnlarr doesn't need to be in
* a single block.
*/
return idx;
}
int mfs_jrnl_updatedinfo(FAR const struct mfs_sb_s * const sb,
FAR struct mfs_path_s * const path,
const mfs_t depth)
{
int ret = OK;
mfs_t blkidx;
mfs_t counter = 0;
mfs_t pg_in_block;
struct mfs_jrnl_log_s tmplog;
/* TODO: Allow optional filling of updated timestamps, etc. */
DEBUGASSERT(depth > 0);
blkidx = MFS_JRNL(sb).log_sblkidx;
pg_in_block = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb);
while (blkidx < MFS_JRNL(sb).n_blks && counter < MFS_JRNL(sb).n_logs)
{
ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_block, &tmplog);
if (predict_false(ret < 0 && ret != -ENOSPC))
{
goto errout;
}
else if (ret == -ENOSPC)
{
break;
}
DEBUGASSERT(tmplog.depth > 0);
if (tmplog.depth > depth)
{
/* Not suitable. */
}
else
{
DEBUGASSERT(tmplog.depth > 0);
if (mfs_path_eq(&tmplog.path[tmplog.depth - 1],
&path[tmplog.depth - 1]))
{
path[tmplog.depth - 1].ctz = tmplog.loc_new;
path[tmplog.depth - 1].sz = tmplog.sz_new;
}
}
mfs_jrnl_log_free(&tmplog);
counter++;
}
errout:
return ret;
}
int mfs_jrnl_wrlog(FAR struct mfs_sb_s * const sb,
FAR const struct mfs_node_s *node,
const struct mfs_ctz_s loc_new, const mfs_t sz_new)
{
int ret = OK;
mfs_t jrnl_pg;
FAR char *buf = NULL;
FAR char *tmp = NULL;
const mfs_t log_sz = sizeof(mfs_t) + MFS_LOGSZ(node->depth);
struct mfs_jrnl_log_s log;
buf = fs_heap_zalloc(log_sz); /* For size before log. */
if (predict_false(buf == NULL))
{
ret = -ENOMEM;
goto errout;
}
/* Serialize */
log.depth = node->depth;
log.sz_new = sz_new;
log.loc_new = loc_new;
log.st_mtim_new = node->st_mtim;
log.st_atim_new = node->st_atim;
log.st_ctim_new = node->st_ctim;
log.path = node->path; /* Fine as temporarily usage. */
tmp = buf;
tmp = mfs_ser_mfs(log_sz - sizeof(mfs_t), tmp); /* First 4 bytes have sz */
tmp = ser_log(&log, tmp);
/* Store */
jrnl_pg = MFS_JRNL(sb).log_cpg;
/* TODO: It assumes it takes only one page per log. */
ret = mfs_write_page(sb, buf, log_sz, jrnl_pg, 0);
if (predict_false(ret < 0))
{
goto errout_with_buf;
}
ret = OK;
jrnl_pg++;
if (jrnl_pg % MFS_PGINBLK(sb) == 0)
{
MFS_JRNL(sb).log_cblkidx++;
}
MFS_JRNL(sb).log_cpg = jrnl_pg;
MFS_JRNL(sb).n_logs++;
errout_with_buf:
fs_heap_free(buf);
errout:
return ret;
}
int mfs_jrnl_flush(FAR struct mfs_sb_s * const sb)
{
/* When a file or a directory is deleted.
*
* It will be modified to an entry in the LRU which details the deletion
* of all bytes from the child... as in, offset 0, deleted bytes is the
* size of the file.
*
* The new "location" can be used as (0, 0) to signify a deletion, even in
* its journal log.
*
* Also ensure if the size gets updated to 0.
*
* Then the flush operation problem will be solved for removal of files or
* directories.
*
* Move operation will not empty the child, but only the parent from the
* old parent.
*/
/* Time complexity is going to be horrendous. Hint: O(n^2). HOWEVER, as
* littlefs points out....if n is constant, it's essentially a O(k), or
* O(1) :D
*/
/* TODO: Need to consider how the LRU and Journal interact with each other
* for newly created fs object's entries.
*/
/* We're using updatectz to update the LRU inside the journal. Think
* about how that might affect the iteration attempts.
*/
int ret = OK;
mfs_t blkidx = MFS_JRNL(sb).log_sblkidx;
mfs_t log_itr = 0;
mfs_t pg_in_blk = MFS_JRNL(sb).log_spg \
% MFS_PGINBLK(sb);
mfs_t tmp_blkidx;
mfs_t tmp_pg_in_blk;
mfs_t mn_blk1;
mfs_t mn_blk2;
mfs_t i;
mfs_t jrnl_blk;
mfs_t blk;
struct mfs_jrnl_log_s log;
struct mfs_jrnl_log_s tmp_log;
FAR struct mfs_path_s *path = NULL;
struct mfs_jrnl_state_s j_state;
struct mfs_mn_s mn_state;
while (log_itr < MFS_JRNL(sb).n_logs)
{
ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log);
if (predict_false(ret < 0))
{
DEBUGASSERT(ret != -ENOSPC); /* While condition is sufficient. */
goto errout;
}
if (log.loc_new.pg_e == 0 && log.loc_new.idx_e == 0)
{
/* Entry is deleted, do not bother with it. */
break;
}
tmp_blkidx = blkidx;
tmp_pg_in_blk = pg_in_blk;
path = fs_heap_zalloc(log.depth * sizeof(struct mfs_path_s));
if (predict_false(path == NULL))
{
goto errout;
}
memcpy(path, log.path, log.depth * sizeof(struct mfs_path_s));
path[log.depth - 1].ctz = log.loc_new;
for (; ; )
{
ret = mfs_jrnl_rdlog(sb, &tmp_blkidx, &tmp_pg_in_blk, &tmp_log);
if (ret == -ENOSPC)
{
break;
}
else if (predict_false(ret < 0))
{
mfs_jrnl_log_free(&log);
mfs_free_patharr(path);
goto errout;
}
if (tmp_log.depth > log.depth)
{
mfs_jrnl_log_free(&tmp_log);
continue;
}
if (!mfs_path_eq(&path[tmp_log.depth - 1],
&tmp_log.path[tmp_log.depth - 1]))
{
mfs_jrnl_log_free(&tmp_log);
continue;
}
path[tmp_log.depth - 1] = tmp_log.path[tmp_log.depth - 1];
if (tmp_log.loc_new.pg_e == 0 && tmp_log.loc_new.idx_e == 0)
{
/* Entry is deleted, do not bother with it. */
break;
}
}
if (log.depth == 1)
{
MFS_MN(sb).root_ctz = path[log.depth - 1].ctz;
MFS_MN(sb).root_sz = path[log.depth - 1].sz;
/* TODO: Other parameters. */
}
else
{
ret = mfs_lru_updatectz(sb, path, log.depth,
path[log.depth - 1].ctz,
path[log.depth - 1].sz);
if (predict_false(ret < 0))
{
mfs_free_patharr(path);
mfs_jrnl_log_free(&log);
goto errout;
}
}
mfs_free_patharr(path);
mfs_jrnl_log_free(&log);
}
if (MFS_MN(sb).mblk_idx == MFS_PGINBLK(sb))
{
mn_blk1 = 0;
mn_blk2 = 0;
}
else
{
/* FUTURE TODO: Save the two block numbers in master node structure to
* be faster.
*/
mn_blk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks);
mn_blk2 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks + 1);
}
/* Reallocate journal. */
j_state = MFS_JRNL(sb);
mn_state = MFS_MN(sb);
ret = mfs_jrnl_fmt(sb, &mn_blk1, &mn_blk2, &jrnl_blk);
if (predict_false(ret < 0))
{
MFS_JRNL(sb) = j_state;
goto errout;
}
/* Write master node entry. */
ret = mfs_mn_sync(sb, &path[0], mn_blk1, mn_blk2, jrnl_blk);
if (predict_false(ret < 0))
{
MFS_MN(sb) = mn_state;
goto errout;
}
/* Mark all old blocks of journal (and master blocks) as deletable. */
for (i = 0; i < MFS_JRNL(sb).n_blks + 2; i++)
{
blk = mfs_jrnl_blkidx2blk(sb, i);
mfs_ba_blkmarkdel(sb, blk);
}
/* Delete outdated blocks. */
ret = mfs_ba_delmarked(sb);
if (predict_false(ret < 0))
{
goto errout;
}
errout:
return ret;
}
bool mfs_jrnl_isempty(FAR const struct mfs_sb_s * const sb)
{
return MFS_JRNL(sb).n_logs == 0;
}

File diff suppressed because it is too large Load diff

View file

@ -1,453 +0,0 @@
/****************************************************************************
* fs/mnemofs/mnemofs_master.c
*
* SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
* Alternatively, the contents of this file may be used under the terms of
* the BSD-3-Clause license:
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024 Saurav Pal
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors may
* be used to endorse or promote products derived from this software
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* In mnemofs, the master node points to the root of the file system. It
* contains the information about the root, and when the root is updated,
* the master node needs to point to the updated location, and thus, needs to
* update the master node.
*
* Master nodes sit at the very end of the journal. The last two blocks of
* the journal are called master blocks, and they are filled with a new
* entry for a master node every time it is updated. They are filled in a
* sequential manner, and thus, the latest master node can be found easily.
* The two master blocks contain identical information, and exist to be as a
* backup.
*
* The stored master nodes are basically `struct mfs_mn_s` without the
* redundant `pg` member.
*
* The master node also points to the start of the journal, and thus, when
* the journal moves, a new master node entry is added.
*
* A master node update, when written to the file system, marks the end of
* an update of the file system tree. Thus, at this point, any obsolete data
* that can be erased, will be erased by the block allocator. Only after
* writing the master block is the file system tree updated. Before this,
* the old file system tree is accessible through the older master node, and
* can be accessed again during power loss.
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/kmalloc.h>
#include <sys/stat.h>
#include "mnemofs.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
static FAR char *ser_mn(const struct mfs_mn_s mn,
FAR char * const out);
static FAR const char *deser_mn(FAR const char * const in,
FAR struct mfs_mn_s *mn, FAR uint16_t *hash);
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: ser_mn
*
* Description:
* Serialize master node.
*
* Input Parameters:
* mn - Master node.
* out - Out buffer.
*
* Returned Value:
* Pointer to the end of the serialized data in `out`.
*
* Assumptions/Limitations:
* Out should contain enough space for `mn` and 1 byte extra for the hash.
*
****************************************************************************/
static FAR char *ser_mn(const struct mfs_mn_s mn, FAR char * const out)
{
FAR char *tmp = out;
tmp = mfs_ser_mfs(mn.jrnl_blk, tmp);
tmp = mfs_ser_mfs(mn.mblk_idx, tmp);
tmp = mfs_ser_ctz(&mn.root_ctz, tmp);
tmp = mfs_ser_mfs(mn.root_sz, tmp);
tmp = mfs_ser_timespec(&mn.ts, tmp);
tmp = mfs_ser_16(mfs_hash(out, tmp - out), tmp);
/* TODO: Update this, and the make a macro for size of MN. */
return tmp;
}
/****************************************************************************
* Name: ser_mn
*
* Description:
* Deserialize master node.
*
* Input Parameters:
* in - In buffer.
* mn - Master node to populate.
* hash - Stored hash (of serialized data) to populate.
*
* Returned Value:
* Pointer to the end of the serialized data in `in`.
*
* Assumptions/Limitations:
* In should contain enough space for `mn` and 1 byte extra for the hash.
*
****************************************************************************/
static FAR const char *deser_mn(FAR const char * const in,
FAR struct mfs_mn_s *mn, FAR uint16_t *hash)
{
FAR const char *tmp = in;
tmp = mfs_deser_mfs(tmp, &mn->jrnl_blk);
tmp = mfs_deser_mfs(tmp, &mn->mblk_idx);
tmp = mfs_deser_ctz(tmp, &mn->root_ctz);
tmp = mfs_deser_mfs(tmp, &mn->root_sz);
tmp = mfs_deser_timespec(tmp, &mn->ts);
tmp = mfs_deser_16(tmp, hash);
/* TODO: Update this, and the make a macro for size of MN. */
return tmp;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int mfs_mn_init(FAR struct mfs_sb_s * const sb, const mfs_t jrnl_blk)
{
int ret = OK;
bool found = false;
mfs_t i = 0;
mfs_t mblk1;
mfs_t blkidx;
mfs_t pg_in_blk;
mfs_t jrnl_blk_tmp;
uint16_t hash;
struct mfs_mn_s mn;
const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg);
char buftmp[4];
char buf[sz + 1];
struct mfs_jrnl_log_s log;
mblk1 = mfs_jrnl_blkidx2blk(sb, MFS_JRNL(sb).n_blks);
mn.jrnl_blk = jrnl_blk;
mn.mblk_idx = 0;
mn.pg = MFS_BLK2PG(sb, mblk1);
for (i = 0; i < MFS_PGINBLK(sb); i++)
{
mfs_read_page(sb, buftmp, 4, mn.pg, 0);
mfs_deser_mfs(buftmp, &jrnl_blk_tmp);
if (jrnl_blk_tmp == 0)
{
break;
}
found = true;
mn.mblk_idx++;
mn.pg++;
}
if (found == false)
{
ret = -EINVAL;
goto errout;
}
if (i == MFS_PGINBLK(sb))
{
ret = -ENOSPC;
goto errout;
}
else
{
mn.pg--;
}
mfs_read_page(sb, buf, sz + 1, mn.pg, 0);
deser_mn(buf, &mn, &hash);
if (hash != mfs_hash(buf, sz))
{
ret = -EINVAL;
goto errout;
}
blkidx = MFS_JRNL(sb).log_sblkidx;
pg_in_blk = MFS_JRNL(sb).log_spg % MFS_PGINBLK(sb);
while (true)
{
ret = mfs_jrnl_rdlog(sb, &blkidx, &pg_in_blk, &log);
if (predict_false(ret < 0 && ret != -ENOSPC))
{
goto errout;
}
else if (ret == -ENOSPC)
{
ret = OK;
break;
}
/* Assumes checking the depth is enough to check if it's empty, as
* theoretically there are no blocks with depth 0, as root has a
* depth of 1.
*/
if (log.depth == 0)
{
DEBUGASSERT(log.path == NULL);
break;
}
if (log.depth == 1)
{
mn.root_ctz = log.loc_new;
mn.root_sz = log.sz_new;
}
mfs_jrnl_log_free(&log);
}
/* FUTURE TODO: Recovery in case of hash not matching, or page not
* readable.
*/
mn.root_mode = 0777 | S_IFDIR;
MFS_MN(sb) = mn;
errout:
return ret;
}
int mfs_mn_fmt(FAR struct mfs_sb_s * const sb, const mfs_t mblk1,
const mfs_t mblk2, const mfs_t jrnl_blk)
{
int ret = OK;
mfs_t pg;
struct mfs_mn_s mn;
struct timespec ts;
const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg);
char buf[sz + 1];
clock_gettime(CLOCK_REALTIME, &ts);
memset(buf, 0, sz + 1);
pg = mfs_ba_getpg(sb);
if (predict_false(pg == 0))
{
ret = -ENOSPC;
goto errout;
}
finfo("Root formatted to be at Page %u", pg);
mn.root_ctz.idx_e = 0;
mn.root_ctz.pg_e = pg;
mn.jrnl_blk = jrnl_blk;
mn.mblk_idx = 0;
mn.pg = MFS_BLK2PG(sb, mblk1);
mn.root_sz = 0;
mn.ts = ts;
mn.root_st_atim = ts;
mn.root_st_ctim = ts;
mn.root_st_mtim = ts;
mn.root_mode = 0777 | S_IFDIR;
/* Serialize. */
ser_mn(mn, buf);
ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, mblk1), 0);
if (predict_false(ret < 0))
{
goto errout;
}
else
{
ret = OK;
}
ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, mblk2), 0);
if (predict_false(ret < 0))
{
goto errout;
}
else
{
ret = OK;
}
mn.mblk_idx = 1;
MFS_MN(sb) = mn;
finfo("Master node written. Now at page %d, timestamp %lld.%.9ld.",
MFS_MN(sb).pg, (long long)MFS_MN(sb).ts.tv_sec,
MFS_MN(sb).ts.tv_nsec);
errout:
return ret;
}
int mfs_mn_move(FAR struct mfs_sb_s * const sb, struct mfs_ctz_s root,
const mfs_t root_sz)
{
int ret = OK;
struct mfs_mn_s mn;
const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg);
char buf[sz + 1];
if (MFS_MN(sb).mblk_idx == MFS_PGINBLK(sb) - 1)
{
/* TODO: Move journal. Master blocks are full. */
}
mn = MFS_MN(sb);
mn.root_ctz = root;
mn.root_sz = root_sz;
mn.mblk_idx++; /* TODO */
mn.pg++;
ser_mn(mn, buf);
ret = mfs_write_page(sb, buf, sz + 1, mn.pg, 0);
if (predict_false(ret < 0))
{
goto errout;
}
MFS_MN(sb) = mn;
errout:
return ret;
}
int mfs_mn_sync(FAR struct mfs_sb_s *sb,
FAR struct mfs_path_s * const new_loc,
const mfs_t blk1, const mfs_t blk2, const mfs_t jrnl_blk)
{
int ret = OK;
struct timespec ts;
struct mfs_mn_s mn;
const mfs_t sz = sizeof(struct mfs_mn_s) - sizeof(mn.pg);
char buf[sz + 1];
mn = MFS_MN(sb);
clock_gettime(CLOCK_REALTIME, &ts);
if (mn.mblk_idx == MFS_PGINBLK(sb))
{
/* New blocks have been already allocated by the journal. */
mn.mblk_idx = 0;
mn.pg = MFS_BLK2PG(sb, blk1);
}
mn.ts = ts;
mn.root_sz = new_loc->sz;
mn.root_ctz = new_loc->ctz;
mn.root_mode = 0777 | S_IFDIR;
/* TODO: Root timestamps. */
/* Serialize. */
ser_mn(mn, buf);
ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, blk1) + mn.mblk_idx, 0);
if (predict_false(ret < 0))
{
goto errout;
}
ret = mfs_write_page(sb, buf, sz, MFS_BLK2PG(sb, blk2) + mn.mblk_idx, 0);
if (predict_false(ret < 0))
{
goto errout;
}
mn.mblk_idx++;
MFS_MN(sb) = mn;
errout:
return ret;
}

View file

@ -54,129 +54,503 @@
* Included Files
****************************************************************************/
#include <sys/param.h>
#include <errno.h>
#include <string.h>
#include "mnemofs.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: mfs_rwbuf_check_page
*
* Description:
* Verify that page belongs to a good NAND block before it is accessed
* through the shared read/write buffer.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page number to validate.
*
* Returned Value:
* Zero (OK) is returned if page is valid and its block is not marked bad.
* A negated errno value is returned on invalid input or if the page maps
* to a bad block.
*
****************************************************************************/
static int mfs_rwbuf_check_page(FAR struct mfs_sb_s *sb, mfs_t page)
{
mfs_t block;
int ret;
if (sb == NULL || sb->mtd == NULL)
{
return -EINVAL;
}
if (page >= MFS_PAGE_COUNT(sb))
{
return -EINVAL;
}
block = page / MFS_PAGES_PER_BLOCK(sb);
ret = mfs_is_bad_block(sb, block);
if (ret < 0)
{
return ret;
}
return ret == 0 ? OK : -EIO;
}
/****************************************************************************
* Name: mfs_rwbuf_load
*
* Description:
* Load one page into the shared read/write buffer, syncing any dirty
* contents already cached there first.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page number to load.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned if
* validation, syncing, or the underlying read fails.
*
****************************************************************************/
static int mfs_rwbuf_load(FAR struct mfs_sb_s *sb, mfs_t page)
{
ssize_t nread;
int ret;
if (sb == NULL || sb->rwbuf == NULL)
{
return -EINVAL;
}
if (sb->rwvalid && sb->rwpage == page)
{
return OK;
}
ret = mfs_rwbuf_sync(sb);
if (ret < 0)
{
return ret;
}
ret = mfs_rwbuf_check_page(sb, page);
if (ret < 0)
{
return ret;
}
nread = MTD_BREAD(sb->mtd, page, 1, sb->rwbuf);
if (nread < 0)
{
return (int)nread;
}
if (nread != 1)
{
return -EIO;
}
sb->rwpage = page;
sb->rwvalid = true;
sb->rwdirty = false;
return OK;
}
/****************************************************************************
* Public Functions
****************************************************************************/
int mfs_isbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk)
/****************************************************************************
* Name: mfs_is_bad_block
*
* Description:
* Query the backing MTD device to learn whether blk is marked bad.
*
* Input Parameters:
* sb - The mounted file system instance.
* blk - The block number to test.
*
* Returned Value:
* Zero (OK) is returned if blk is usable. A positive non-zero value may
* be returned if the MTD reports blk as bad. A negated errno value is
* returned on invalid input or device failure.
*
****************************************************************************/
int mfs_is_bad_block(FAR const struct mfs_sb_s *sb, mfs_t blk)
{
if (predict_false(blk > MFS_NBLKS(sb)))
int ret;
if (sb == NULL || sb->mtd == NULL)
{
ferr("invalid args\n");
return -EINVAL;
}
return MTD_ISBAD(MFS_MTD(sb), blk);
if (blk >= MFS_BLOCK_COUNT(sb))
{
ferr("invalid block\n");
return -EINVAL;
}
ret = MTD_ISBAD(sb->mtd, blk);
if (ret < 0 && ret != -ENOSYS)
{
ferr("MTD_ISBAD failed: %d\n", ret);
}
return ret == -ENOSYS ? 0 : ret;
}
int mfs_markbadblk(FAR const struct mfs_sb_s * const sb, mfs_t blk)
/****************************************************************************
* Name: mfs_rwbuf_sync
*
* Description:
* Flush dirty contents from the shared read/write buffer to its cached
* page.
*
* Input Parameters:
* sb - The mounted file system instance.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned if
* the buffered page cannot be written back.
*
****************************************************************************/
int mfs_rwbuf_sync(FAR struct mfs_sb_s *sb)
{
if (predict_false(blk > MFS_NBLKS(sb)))
ssize_t nwritten;
if (sb == NULL)
{
ferr("invalid sb\n");
return -EINVAL;
}
return MTD_MARKBAD(MFS_MTD(sb), blk);
if (sb->rwbuf == NULL || !sb->rwvalid || !sb->rwdirty)
{
return OK;
}
nwritten = MTD_BWRITE(sb->mtd, sb->rwpage, 1, sb->rwbuf);
if (nwritten < 0)
{
ferr("MTD_BWRITE failed: %zd\n", nwritten);
return (int)nwritten;
}
if (nwritten != 1)
{
ferr("short write: %zd\n", nwritten);
return -EIO;
}
sb->rwdirty = false;
return OK;
}
/* NOTE: These functions do not update the block allocator's state nor do
* they enforce it.
*/
/****************************************************************************
* Name: mfs_rwbuf_invalidate
*
* Description:
* Drop any cached page association from the shared read/write buffer.
*
* Input Parameters:
* sb - The mounted file system instance.
*
* Returned Value:
* None.
*
****************************************************************************/
ssize_t mfs_write_page(FAR const struct mfs_sb_s * const sb,
FAR const char *data, const mfs_t datalen,
const off_t page, const mfs_t pgoff)
void mfs_rwbuf_invalidate(FAR struct mfs_sb_s *sb)
{
int ret = OK;
if (predict_false(page > MFS_NPGS(sb) || pgoff >= MFS_PGSZ(sb)))
if (sb == NULL)
{
return;
}
sb->rwpage = MFS_LOCATION_INVALID;
sb->rwvalid = false;
sb->rwdirty = false;
}
/****************************************************************************
* Name: mfs_rwbuf_prepare_write
*
* Description:
* Sync any dirty buffered page and then invalidate the cache so the next
* write starts from a clean state.
*
* Input Parameters:
* sb - The mounted file system instance.
*
* Returned Value:
* Zero (OK) is returned on success. A negated errno value is returned if
* syncing the buffered page fails.
*
****************************************************************************/
int mfs_rwbuf_prepare_write(FAR struct mfs_sb_s *sb)
{
int ret;
if (sb == NULL)
{
ferr("invalid sb\n");
return -EINVAL;
}
memcpy(MFS_RWBUF(sb) + pgoff, data, MIN(datalen, MFS_PGSZ(sb) - pgoff));
ret = MTD_BWRITE(MFS_MTD(sb), page, 1, MFS_RWBUF(sb));
if (predict_false(ret < 0))
ret = mfs_rwbuf_sync(sb);
if (ret < 0)
{
goto errout_with_reset;
ferr("mfs_rwbuf_sync failed: %d\n", ret);
return ret;
}
errout_with_reset:
memset(MFS_RWBUF(sb), 0, MFS_PGSZ(sb));
mfs_rwbuf_invalidate(sb);
return OK;
}
/****************************************************************************
* Name: mfs_rwbuf_discard_page
*
* Description:
* Invalidate the shared read/write buffer if it currently caches page.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page whose cached state should be discarded.
*
* Returned Value:
* None.
*
****************************************************************************/
void mfs_rwbuf_discard_page(FAR struct mfs_sb_s *sb, mfs_t page)
{
if (sb == NULL || !sb->rwvalid)
{
return;
}
if (sb->rwpage == page)
{
mfs_rwbuf_invalidate(sb);
}
}
/****************************************************************************
* Name: mfs_rwbuf_discard_block
*
* Description:
* Invalidate the shared read/write buffer if it currently caches any page
* that belongs to block.
*
* Input Parameters:
* sb - The mounted file system instance.
* block - The block whose cached state should be discarded.
*
* Returned Value:
* None.
*
****************************************************************************/
void mfs_rwbuf_discard_block(FAR struct mfs_sb_s *sb, mfs_t block)
{
if (sb == NULL || block >= MFS_BLOCK_COUNT(sb) || !sb->rwvalid)
{
return;
}
if (sb->rwpage / MFS_PAGES_PER_BLOCK(sb) == block)
{
mfs_rwbuf_invalidate(sb);
}
}
/****************************************************************************
* Name: mfs_write_page
*
* Description:
* Stage one page for writing through the shared read/write buffer. The
* page becomes dirty and is written later by mfs_rwbuf_sync().
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page number to update.
* buffer - The page-sized data to stage.
*
* Returned Value:
* One is returned on success. A negated errno value is returned if the
* inputs are invalid, the page cannot be cached, or an earlier dirty page
* cannot be synced.
*
****************************************************************************/
ssize_t mfs_write_page(FAR struct mfs_sb_s *sb, mfs_t page,
FAR const uint8_t *buffer)
{
int ret;
if (sb == NULL || sb->mtd == NULL || sb->rwbuf == NULL || buffer == NULL)
{
ferr("invalid args\n");
return -EINVAL;
}
if (page >= MFS_PAGE_COUNT(sb))
{
ferr("invalid page\n");
return -EINVAL;
}
if (!sb->rwvalid || sb->rwpage != page)
{
ret = mfs_rwbuf_sync(sb);
if (ret < 0)
{
ferr("mfs_rwbuf_sync failed: %d\n", ret);
return ret;
}
ret = mfs_rwbuf_check_page(sb, page);
if (ret < 0)
{
ferr("mfs_rwbuf_check_page failed: %d\n", ret);
return ret;
}
sb->rwpage = page;
sb->rwvalid = true;
sb->rwdirty = false;
}
if (buffer != sb->rwbuf)
{
memcpy(sb->rwbuf, buffer, MFS_PAGE_SIZE(sb));
}
sb->rwdirty = true;
return 1;
}
/****************************************************************************
* Name: mfs_read_page
*
* Description:
* Load one page through the shared read/write buffer and copy it to the
* caller if needed.
*
* Input Parameters:
* sb - The mounted file system instance.
* page - The page number to read.
* buffer - The page-sized buffer that receives the data.
*
* Returned Value:
* One is returned on success. A negated errno value is returned if the
* inputs are invalid or the page cannot be loaded.
*
****************************************************************************/
ssize_t mfs_read_page(FAR struct mfs_sb_s *sb, mfs_t page,
FAR uint8_t *buffer)
{
int ret;
if (sb == NULL || sb->mtd == NULL || sb->rwbuf == NULL || buffer == NULL)
{
ferr("invalid args\n");
return -EINVAL;
}
if (page >= MFS_PAGE_COUNT(sb))
{
ferr("invalid page\n");
return -EINVAL;
}
ret = mfs_rwbuf_load(sb, page);
if (ret < 0)
{
ferr("mfs_rwbuf_load failed: %d\n", ret);
return ret;
}
if (buffer != sb->rwbuf)
{
memcpy(buffer, sb->rwbuf, MFS_PAGE_SIZE(sb));
}
return 1;
}
/****************************************************************************
* Name: mfs_erase_blocks
*
* Description:
* Erase a contiguous range of blocks and discard any cached page that
* falls inside that range.
*
* Input Parameters:
* sb - The mounted file system instance.
* startblk - The first block to erase.
* nblocks - The number of blocks to erase.
*
* Returned Value:
* The underlying MTD erase result is returned on success. A negated
* errno value is returned on invalid input.
*
****************************************************************************/
int mfs_erase_blocks(FAR struct mfs_sb_s *sb, mfs_t startblk,
size_t nblocks)
{
int ret;
if (sb == NULL || sb->mtd == NULL || nblocks == 0)
{
ferr("invalid args\n");
return -EINVAL;
}
if (startblk >= MFS_BLOCK_COUNT(sb) ||
nblocks > MFS_BLOCK_COUNT(sb) ||
startblk > MFS_BLOCK_COUNT(sb) - nblocks)
{
ferr("invalid erase range\n");
return -EINVAL;
}
if (sb->rwvalid)
{
mfs_t endblk = startblk + nblocks;
mfs_t curblk = sb->rwpage / MFS_PAGES_PER_BLOCK(sb);
if (curblk >= startblk && curblk < endblk)
{
mfs_rwbuf_invalidate(sb);
}
}
ret = MTD_ERASE(sb->mtd, startblk, nblocks);
if (ret < 0)
{
ferr("MTD_ERASE failed: %d\n", ret);
}
return ret;
}
ssize_t mfs_read_page(FAR const struct mfs_sb_s * const sb,
FAR char *data, const mfs_t datalen, const off_t page,
const mfs_t pgoff)
{
int ret = OK;
if (predict_false(page > MFS_NPGS(sb) || pgoff >= MFS_PGSZ(sb)))
{
return -EINVAL;
}
ret = MTD_BREAD(MFS_MTD(sb), page, 1, MFS_RWBUF(sb));
if (predict_false(ret < 0))
{
goto errout_with_reset;
}
memcpy(data, MFS_RWBUF(sb) + pgoff, MIN(datalen, MFS_PGSZ(sb) - pgoff));
errout_with_reset:
memset(MFS_RWBUF(sb), 0, MFS_PGSZ(sb));
return ret;
}
int mfs_erase_blk(FAR const struct mfs_sb_s * const sb, const off_t blk)
{
if (predict_false(blk > MFS_NBLKS(sb)))
{
return -EINVAL;
}
return MTD_ERASE(MFS_MTD(sb), blk, 1);
}
int mfs_erase_nblks(FAR const struct mfs_sb_s * const sb, const off_t blk,
const size_t n)
{
if (predict_false(blk + n > MFS_NBLKS(sb)))
{
return -EINVAL;
}
return MTD_ERASE(MFS_MTD(sb), blk, n);
}

View file

@ -1,263 +0,0 @@
/****************************************************************************
* fs/mnemofs/mnemofs_util.c
*
* SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
* Alternatively, the contents of this file may be used under the terms of
* the BSD-3-Clause license:
*
* SPDX-License-Identifier: BSD-3-Clause
*
* Copyright (c) 2024 Saurav Pal
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of its contributors may
* be used to endorse or promote products derived from this software
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include "mnemofs.h"
#include <sys/param.h>
#include <stdio.h>
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/****************************************************************************
* Private Types
****************************************************************************/
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/****************************************************************************
* Private Data
****************************************************************************/
/****************************************************************************
* Public Data
****************************************************************************/
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Public Functions
****************************************************************************/
uint16_t mfs_hash(FAR const char *arr, ssize_t len)
{
ssize_t l = 0;
ssize_t r = len - 1;
uint32_t hash = 0;
while (l <= r)
{
hash += arr[l] * arr[r] * (l + 1) * (r + 1);
l++;
r--;
hash %= (1 << MFS_HASHSZ);
}
finfo("Hash calculated for size %zd to be %" PRIi32, len, hash);
return hash;
}
FAR char *mfs_ser_8(const uint8_t n, FAR char * const out)
{
*out = n;
return out + 1;
}
FAR const char *mfs_deser_8(FAR const char * const in, FAR uint8_t *n)
{
*n = in[0];
return in + 1;
}
FAR char *mfs_ser_16(const uint16_t n, FAR char * const out)
{
memcpy(out, &n, 2);
return out + 2;
}
FAR const char *mfs_deser_16(FAR const char * const in, FAR uint16_t *n)
{
memcpy(n, in, 2);
return in + 2;
}
FAR char *mfs_ser_str(FAR const char * const str, const mfs_t len,
FAR char * const out)
{
memcpy(out, str, len);
return out + len;
}
FAR const char *mfs_deser_str(FAR const char * const in,
FAR char * const str, const mfs_t len)
{
memcpy(str, in, len);
str[len] = 0;
return in + len;
}
FAR char *mfs_ser_mfs(const mfs_t n, FAR char * const out)
{
memcpy(out, &n, 4);
return out + 4;
}
FAR const char *mfs_deser_mfs(FAR const char * const in, FAR mfs_t * const n)
{
memcpy(n, in, 4);
return in + 4;
}
FAR char *mfs_ser_64(const uint64_t n, FAR char * const out)
{
memcpy(out, &n, 8);
return out + 8;
}
FAR const char *mfs_deser_64(FAR const char * const in,
FAR uint64_t * const n)
{
memcpy(n, in, 8);
return in + 8;
}
FAR char *mfs_ser_ctz(FAR const struct mfs_ctz_s * const x,
FAR char * const out)
{
FAR char *o = out;
o = mfs_ser_mfs(x->pg_e, o);
o = mfs_ser_mfs(x->idx_e, o);
return o;
}
FAR const char *mfs_deser_ctz(FAR const char * const in,
FAR struct mfs_ctz_s * const x)
{
FAR const char *i = in;
i = mfs_deser_mfs(i, &x->pg_e);
i = mfs_deser_mfs(i, &x->idx_e);
return i;
}
FAR char *mfs_ser_path(FAR const struct mfs_path_s * const x,
FAR char * const out)
{
FAR char *o = out;
o = mfs_ser_ctz(&x->ctz, o);
o = mfs_ser_mfs(x->off, o);
o = mfs_ser_mfs(x->sz, o);
return o;
}
FAR const char *mfs_deser_path(FAR const char * const in,
FAR struct mfs_path_s * const x)
{
FAR const char *i = in;
i = mfs_deser_ctz(i, &x->ctz);
i = mfs_deser_mfs(i, &x->off);
i = mfs_deser_mfs(i, &x->sz);
return i;
}
FAR char *mfs_ser_timespec(FAR const struct timespec * const x,
FAR char * const out)
{
FAR char *o = out;
o = mfs_ser_64(x->tv_sec, o);
o = mfs_ser_64(x->tv_nsec, o);
return o;
}
FAR const char *mfs_deser_timespec(FAR const char * const in,
FAR struct timespec * const x)
{
uint64_t tmp;
FAR const char *i = in;
i = mfs_deser_64(i, &tmp);
x->tv_sec = tmp;
i = mfs_deser_64(i, &tmp);
x->tv_nsec = tmp;
return i;
}
mfs_t mfs_v2n(mfs_t n)
{
/* https://math.stackexchange.com/a/1835555 */
return (n & (~(n - 1)));
}
mfs_t mfs_set_msb(mfs_t n)
{
return 31 - mfs_clz(n);
}
bool mfs_ctz_eq(FAR const struct mfs_ctz_s * const a,
FAR const struct mfs_ctz_s * const b)
{
return a->idx_e == b->idx_e && a->pg_e == b->pg_e;
}
bool mfs_path_eq(FAR const struct mfs_path_s * const a,
FAR const struct mfs_path_s * const b)
{
return mfs_ctz_eq(&a->ctz, &b->ctz) && (a->off == b->off)
&& (a->sz == b->sz);
}