Replace LittleFS with MockFS

This commit is contained in:
2026-01-23 17:51:59 +01:00
parent 028f25b942
commit bde2d5a1ca
9 changed files with 1130 additions and 7853 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
gcc src/sha256.c src/basic.c src/file_system.c src/byte_queue.c src/file_tree.c src/message.c src/tcp.c src/wal.c src/hash_set.c src/client.c src/metadata_server.c src/chunk_server.c src/random_client.c src/main.c quakey/src/lfs.c quakey/src/lfs_util.c quakey/src/quakey.c -o toasty_simulation -Iquakey/include -Iinclude -Wall -Wextra -ggdb -O0 -DMAIN_SIMULATION
gcc src/sha256.c src/basic.c src/file_system.c src/byte_queue.c src/file_tree.c src/message.c src/tcp.c src/wal.c src/hash_set.c src/client.c src/metadata_server.c src/chunk_server.c src/random_client.c src/main.c quakey/src/mockfs.c quakey/src/quakey.c -o toasty_simulation -Iquakey/include -Iinclude -Wall -Wextra -ggdb -O0 -DMAIN_SIMULATION
gcc src/sha256.c src/basic.c src/file_system.c src/byte_queue.c src/file_tree.c src/message.c src/tcp.c src/wal.c src/hash_set.c src/client.c src/metadata_server.c src/chunk_server.c src/random_client.c src/main.c -o toasty_metadata_server -DMAIN_METADATA_SERVER -Wall -Wextra -ggdb -O0 -Iinclude -Iquakey/include
gcc src/sha256.c src/basic.c src/file_system.c src/byte_queue.c src/file_tree.c src/message.c src/tcp.c src/wal.c src/hash_set.c src/client.c src/metadata_server.c src/chunk_server.c src/random_client.c src/main.c -o toasty_chunk_server -DMAIN_CHUNK_SERVER -Wall -Wextra -ggdb -O0 -Iinclude -Iquakey/include
-6548
View File
File diff suppressed because it is too large Load Diff
-801
View File
@@ -1,801 +0,0 @@
/*
* The little filesystem
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS_H
#define LFS_H
#include "lfs_util.h"
#ifdef __cplusplus
extern "C"
{
#endif
/// Version info ///
// Software library version
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_VERSION 0x0002000b
#define LFS_VERSION_MAJOR (0xffff & (LFS_VERSION >> 16))
#define LFS_VERSION_MINOR (0xffff & (LFS_VERSION >> 0))
// Version of On-disk data structures
// Major (top-nibble), incremented on backwards incompatible changes
// Minor (bottom-nibble), incremented on feature additions
#define LFS_DISK_VERSION 0x00020001
#define LFS_DISK_VERSION_MAJOR (0xffff & (LFS_DISK_VERSION >> 16))
#define LFS_DISK_VERSION_MINOR (0xffff & (LFS_DISK_VERSION >> 0))
/// Definitions ///
// Type definitions
typedef uint32_t lfs_size_t;
typedef uint32_t lfs_off_t;
typedef int32_t lfs_ssize_t;
typedef int32_t lfs_soff_t;
typedef uint32_t lfs_block_t;
// Maximum name size in bytes, may be redefined to reduce the size of the
// info struct. Limited to <= 1022. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS_NAME_MAX
#define LFS_NAME_MAX 255
#endif
// Maximum size of a file in bytes, may be redefined to limit to support other
// drivers. Limited on disk to <= 2147483647. Stored in superblock and must be
// respected by other littlefs drivers.
#ifndef LFS_FILE_MAX
#define LFS_FILE_MAX 2147483647
#endif
// Maximum size of custom attributes in bytes, may be redefined, but there is
// no real benefit to using a smaller LFS_ATTR_MAX. Limited to <= 1022. Stored
// in superblock and must be respected by other littlefs drivers.
#ifndef LFS_ATTR_MAX
#define LFS_ATTR_MAX 1022
#endif
// Possible error codes, these are negative to allow
// valid positive return values
enum lfs_error {
LFS_ERR_OK = 0, // No error
LFS_ERR_IO = -5, // Error during device operation
LFS_ERR_CORRUPT = -84, // Corrupted
LFS_ERR_NOENT = -2, // No directory entry
LFS_ERR_EXIST = -17, // Entry already exists
LFS_ERR_NOTDIR = -20, // Entry is not a dir
LFS_ERR_ISDIR = -21, // Entry is a dir
LFS_ERR_NOTEMPTY = -39, // Dir is not empty
LFS_ERR_BADF = -9, // Bad file number
LFS_ERR_FBIG = -27, // File too large
LFS_ERR_INVAL = -22, // Invalid parameter
LFS_ERR_NOSPC = -28, // No space left on device
LFS_ERR_NOMEM = -12, // No more memory available
LFS_ERR_NOATTR = -61, // No data/attr available
LFS_ERR_NAMETOOLONG = -36, // File name too long
};
// File types
enum lfs_type {
// file types
LFS_TYPE_REG = 0x001,
LFS_TYPE_DIR = 0x002,
// internally used types
LFS_TYPE_SPLICE = 0x400,
LFS_TYPE_NAME = 0x000,
LFS_TYPE_STRUCT = 0x200,
LFS_TYPE_USERATTR = 0x300,
LFS_TYPE_FROM = 0x100,
LFS_TYPE_TAIL = 0x600,
LFS_TYPE_GLOBALS = 0x700,
LFS_TYPE_CRC = 0x500,
// internally used type specializations
LFS_TYPE_CREATE = 0x401,
LFS_TYPE_DELETE = 0x4ff,
LFS_TYPE_SUPERBLOCK = 0x0ff,
LFS_TYPE_DIRSTRUCT = 0x200,
LFS_TYPE_CTZSTRUCT = 0x202,
LFS_TYPE_INLINESTRUCT = 0x201,
LFS_TYPE_SOFTTAIL = 0x600,
LFS_TYPE_HARDTAIL = 0x601,
LFS_TYPE_MOVESTATE = 0x7ff,
LFS_TYPE_CCRC = 0x500,
LFS_TYPE_FCRC = 0x5ff,
// internal chip sources
LFS_FROM_NOOP = 0x000,
LFS_FROM_MOVE = 0x101,
LFS_FROM_USERATTRS = 0x102,
};
// File open flags
enum lfs_open_flags {
// open flags
LFS_O_RDONLY = 1, // Open a file as read only
#ifndef LFS_READONLY
LFS_O_WRONLY = 2, // Open a file as write only
LFS_O_RDWR = 3, // Open a file as read and write
LFS_O_CREAT = 0x0100, // Create a file if it does not exist
LFS_O_EXCL = 0x0200, // Fail if a file already exists
LFS_O_TRUNC = 0x0400, // Truncate the existing file to zero size
LFS_O_APPEND = 0x0800, // Move to end of file on every write
#endif
// internally used flags
#ifndef LFS_READONLY
LFS_F_DIRTY = 0x010000, // File does not match storage
LFS_F_WRITING = 0x020000, // File has been written since last flush
#endif
LFS_F_READING = 0x040000, // File has been read since last flush
#ifndef LFS_READONLY
LFS_F_ERRED = 0x080000, // An error occurred during write
#endif
LFS_F_INLINE = 0x100000, // Currently inlined in directory entry
};
// File seek flags
enum lfs_whence_flags {
LFS_SEEK_SET = 0, // Seek relative to an absolute position
LFS_SEEK_CUR = 1, // Seek relative to the current file position
LFS_SEEK_END = 2, // Seek relative to the end of the file
};
// Configuration provided during initialization of the littlefs
struct lfs_config {
// Opaque user provided context that can be used to pass
// information to the block device operations
void *context;
// Read a region in a block. Negative error codes are propagated
// to the user.
int (*read)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, void *buffer, lfs_size_t size);
// Program a region in a block. The block must have previously
// been erased. Negative error codes are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*prog)(const struct lfs_config *c, lfs_block_t block,
lfs_off_t off, const void *buffer, lfs_size_t size);
// Erase a block. A block must be erased before being programmed.
// The state of an erased block is undefined. Negative error codes
// are propagated to the user.
// May return LFS_ERR_CORRUPT if the block should be considered bad.
int (*erase)(const struct lfs_config *c, lfs_block_t block);
// Sync the state of the underlying block device. Negative error codes
// are propagated to the user.
int (*sync)(const struct lfs_config *c);
#ifdef LFS_THREADSAFE
// Lock the underlying block device. Negative error codes
// are propagated to the user.
int (*lock)(const struct lfs_config *c);
// Unlock the underlying block device. Negative error codes
// are propagated to the user.
int (*unlock)(const struct lfs_config *c);
#endif
// Minimum size of a block read in bytes. All read operations will be a
// multiple of this value.
lfs_size_t read_size;
// Minimum size of a block program in bytes. All program operations will be
// a multiple of this value.
lfs_size_t prog_size;
// Size of an erasable block in bytes. This does not impact ram consumption
// and may be larger than the physical erase size. However, non-inlined
// files take up at minimum one block. Must be a multiple of the read and
// program sizes.
lfs_size_t block_size;
// Number of erasable blocks on the device. Defaults to block_count stored
// on disk when zero.
lfs_size_t block_count;
// Number of erase cycles before littlefs evicts metadata logs and moves
// the metadata to another block. Suggested values are in the
// range 100-1000, with large values having better performance at the cost
// of less consistent wear distribution.
//
// Set to -1 to disable block-level wear-leveling.
int32_t block_cycles;
// Size of block caches in bytes. Each cache buffers a portion of a block in
// RAM. The littlefs needs a read cache, a program cache, and one additional
// cache per file. Larger caches can improve performance by storing more
// data and reducing the number of disk accesses. Must be a multiple of the
// read and program sizes, and a factor of the block size.
lfs_size_t cache_size;
// Size of the lookahead buffer in bytes. A larger lookahead buffer
// increases the number of blocks found during an allocation pass. The
// lookahead buffer is stored as a compact bitmap, so each byte of RAM
// can track 8 blocks.
lfs_size_t lookahead_size;
// Threshold for metadata compaction during lfs_fs_gc in bytes. Metadata
// pairs that exceed this threshold will be compacted during lfs_fs_gc.
// Defaults to ~88% block_size when zero, though the default may change
// in the future.
//
// Note this only affects lfs_fs_gc. Normal compactions still only occur
// when full.
//
// Set to -1 to disable metadata compaction during lfs_fs_gc.
lfs_size_t compact_thresh;
// Optional statically allocated read buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *read_buffer;
// Optional statically allocated program buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *prog_buffer;
// Optional statically allocated lookahead buffer. Must be lookahead_size.
// By default lfs_malloc is used to allocate this buffer.
void *lookahead_buffer;
// Optional upper limit on length of file names in bytes. No downside for
// larger names except the size of the info struct which is controlled by
// the LFS_NAME_MAX define. Defaults to LFS_NAME_MAX or name_max stored on
// disk when zero.
lfs_size_t name_max;
// Optional upper limit on files in bytes. No downside for larger files
// but must be <= LFS_FILE_MAX. Defaults to LFS_FILE_MAX or file_max stored
// on disk when zero.
lfs_size_t file_max;
// Optional upper limit on custom attributes in bytes. No downside for
// larger attributes size but must be <= LFS_ATTR_MAX. Defaults to
// LFS_ATTR_MAX or attr_max stored on disk when zero.
lfs_size_t attr_max;
// Optional upper limit on total space given to metadata pairs in bytes. On
// devices with large blocks (e.g. 128kB) setting this to a low size (2-8kB)
// can help bound the metadata compaction time. Must be <= block_size.
// Defaults to block_size when zero.
lfs_size_t metadata_max;
// Optional upper limit on inlined files in bytes. Inlined files live in
// metadata and decrease storage requirements, but may be limited to
// improve metadata-related performance. Must be <= cache_size, <=
// attr_max, and <= block_size/8. Defaults to the largest possible
// inline_max when zero.
//
// Set to -1 to disable inlined files.
lfs_size_t inline_max;
#ifdef LFS_MULTIVERSION
// On-disk version to use when writing in the form of 16-bit major version
// + 16-bit minor version. This limiting metadata to what is supported by
// older minor versions. Note that some features will be lost. Defaults to
// to the most recent minor version when zero.
uint32_t disk_version;
#endif
};
// File info structure
struct lfs_info {
// Type of the file, either LFS_TYPE_REG or LFS_TYPE_DIR
uint8_t type;
// Size of the file, only valid for REG files. Limited to 32-bits.
lfs_size_t size;
// Name of the file stored as a null-terminated string. Limited to
// LFS_NAME_MAX+1, which can be changed by redefining LFS_NAME_MAX to
// reduce RAM. LFS_NAME_MAX is stored in superblock and must be
// respected by other littlefs drivers.
char name[LFS_NAME_MAX+1];
};
// Filesystem info structure
struct lfs_fsinfo {
// On-disk version.
uint32_t disk_version;
// Size of a logical block in bytes.
lfs_size_t block_size;
// Number of logical blocks in filesystem.
lfs_size_t block_count;
// Upper limit on the length of file names in bytes.
lfs_size_t name_max;
// Upper limit on the size of files in bytes.
lfs_size_t file_max;
// Upper limit on the size of custom attributes in bytes.
lfs_size_t attr_max;
};
// Custom attribute structure, used to describe custom attributes
// committed atomically during file writes.
struct lfs_attr {
// 8-bit type of attribute, provided by user and used to
// identify the attribute
uint8_t type;
// Pointer to buffer containing the attribute
void *buffer;
// Size of attribute in bytes, limited to LFS_ATTR_MAX
lfs_size_t size;
};
// Optional configuration provided during lfs_file_opencfg
struct lfs_file_config {
// Optional statically allocated file buffer. Must be cache_size.
// By default lfs_malloc is used to allocate this buffer.
void *buffer;
// Optional list of custom attributes related to the file. If the file
// is opened with read access, these attributes will be read from disk
// during the open call. If the file is opened with write access, the
// attributes will be written to disk every file sync or close. This
// write occurs atomically with update to the file's contents.
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller
// than the buffer, it will be padded with zeros. If the stored attribute
// is larger, then it will be silently truncated. If the attribute is not
// found, it will be created implicitly.
struct lfs_attr *attrs;
// Number of custom attributes in the list
lfs_size_t attr_count;
};
/// internal littlefs data structures ///
typedef struct lfs_cache {
lfs_block_t block;
lfs_off_t off;
lfs_size_t size;
uint8_t *buffer;
} lfs_cache_t;
typedef struct lfs_mdir {
lfs_block_t pair[2];
uint32_t rev;
lfs_off_t off;
uint32_t etag;
uint16_t count;
bool erased;
bool split;
lfs_block_t tail[2];
} lfs_mdir_t;
// littlefs directory type
typedef struct lfs_dir {
struct lfs_dir *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
lfs_off_t pos;
lfs_block_t head[2];
} lfs_dir_t;
// littlefs file type
typedef struct lfs_file {
struct lfs_file *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
struct lfs_ctz {
lfs_block_t head;
lfs_size_t size;
} ctz;
uint32_t flags;
lfs_off_t pos;
lfs_block_t block;
lfs_off_t off;
lfs_cache_t cache;
const struct lfs_file_config *cfg;
} lfs_file_t;
typedef struct lfs_superblock {
uint32_t version;
lfs_size_t block_size;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;
} lfs_superblock_t;
typedef struct lfs_gstate {
uint32_t tag;
lfs_block_t pair[2];
} lfs_gstate_t;
// The littlefs filesystem type
typedef struct lfs {
lfs_cache_t rcache;
lfs_cache_t pcache;
lfs_block_t root[2];
struct lfs_mlist {
struct lfs_mlist *next;
uint16_t id;
uint8_t type;
lfs_mdir_t m;
} *mlist;
uint32_t seed;
lfs_gstate_t gstate;
lfs_gstate_t gdisk;
lfs_gstate_t gdelta;
struct lfs_lookahead {
lfs_block_t start;
lfs_block_t size;
lfs_block_t next;
lfs_block_t ckpoint;
uint8_t *buffer;
} lookahead;
const struct lfs_config *cfg;
lfs_size_t block_count;
lfs_size_t name_max;
lfs_size_t file_max;
lfs_size_t attr_max;
lfs_size_t inline_max;
#ifdef LFS_MIGRATE
struct lfs1 *lfs1;
#endif
} lfs_t;
/// Filesystem functions ///
#ifndef LFS_READONLY
// Format a block device with the littlefs
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_format(lfs_t *lfs, const struct lfs_config *config);
#endif
// Mounts a littlefs
//
// Requires a littlefs object and config struct. Multiple filesystems
// may be mounted simultaneously with multiple littlefs objects. Both
// lfs and config must be allocated while mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_mount(lfs_t *lfs, const struct lfs_config *config);
// Unmounts a littlefs
//
// Does nothing besides releasing any allocated resources.
// Returns a negative error code on failure.
int lfs_unmount(lfs_t *lfs);
/// General operations ///
#ifndef LFS_READONLY
// Removes a file or directory
//
// If removing a directory, the directory must be empty.
// Returns a negative error code on failure.
int lfs_remove(lfs_t *lfs, const char *path);
#endif
#ifndef LFS_READONLY
// Rename or move a file or directory
//
// If the destination exists, it must match the source in type.
// If the destination is a directory, the directory must be empty.
//
// Returns a negative error code on failure.
int lfs_rename(lfs_t *lfs, const char *oldpath, const char *newpath);
#endif
// Find info about a file or directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a negative error code on failure.
int lfs_stat(lfs_t *lfs, const char *path, struct lfs_info *info);
// Get a custom attribute
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. When read, if the stored attribute is smaller than
// the buffer, it will be padded with zeros. If the stored attribute is larger,
// then it will be silently truncated. If no attribute is found, the error
// LFS_ERR_NOATTR is returned and the buffer is filled with zeros.
//
// Returns the size of the attribute, or a negative error code on failure.
// Note, the returned size is the size of the attribute on disk, irrespective
// of the size of the buffer. This can be used to dynamically allocate a buffer
// or check for existence.
lfs_ssize_t lfs_getattr(lfs_t *lfs, const char *path,
uint8_t type, void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Set custom attributes
//
// Custom attributes are uniquely identified by an 8-bit type and limited
// to LFS_ATTR_MAX bytes. If an attribute is not found, it will be
// implicitly created.
//
// Returns a negative error code on failure.
int lfs_setattr(lfs_t *lfs, const char *path,
uint8_t type, const void *buffer, lfs_size_t size);
#endif
#ifndef LFS_READONLY
// Removes a custom attribute
//
// If an attribute is not found, nothing happens.
//
// Returns a negative error code on failure.
int lfs_removeattr(lfs_t *lfs, const char *path, uint8_t type);
#endif
/// File operations ///
#ifndef LFS_NO_MALLOC
// Open a file
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs_open_flags that are bitwise-ored together.
//
// Returns a negative error code on failure.
int lfs_file_open(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags);
// if LFS_NO_MALLOC is defined, lfs_file_open() will fail with LFS_ERR_NOMEM
// thus use lfs_file_opencfg() with config.buffer set.
#endif
// Open a file with extra configuration
//
// The mode that the file is opened in is determined by the flags, which
// are values from the enum lfs_open_flags that are bitwise-ored together.
//
// The config struct provides additional config options per file as described
// above. The config struct must remain allocated while the file is open, and
// the config struct must be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_file_opencfg(lfs_t *lfs, lfs_file_t *file,
const char *path, int flags,
const struct lfs_file_config *config);
// Close a file
//
// Any pending writes are written out to storage as though
// sync had been called and releases any allocated resources.
//
// Returns a negative error code on failure.
int lfs_file_close(lfs_t *lfs, lfs_file_t *file);
// Synchronize a file on storage
//
// Any pending writes are written out to storage.
// Returns a negative error code on failure.
int lfs_file_sync(lfs_t *lfs, lfs_file_t *file);
// Read data from file
//
// Takes a buffer and size indicating where to store the read data.
// Returns the number of bytes read, or a negative error code on failure.
lfs_ssize_t lfs_file_read(lfs_t *lfs, lfs_file_t *file,
void *buffer, lfs_size_t size);
#ifndef LFS_READONLY
// Write data to file
//
// Takes a buffer and size indicating the data to write. The file will not
// actually be updated on the storage until either sync or close is called.
//
// Returns the number of bytes written, or a negative error code on failure.
lfs_ssize_t lfs_file_write(lfs_t *lfs, lfs_file_t *file,
const void *buffer, lfs_size_t size);
#endif
// Change the position of the file
//
// The change in position is determined by the offset and whence flag.
// Returns the new position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_seek(lfs_t *lfs, lfs_file_t *file,
lfs_soff_t off, int whence);
#ifndef LFS_READONLY
// Truncates the size of the file to the specified size
//
// Returns a negative error code on failure.
int lfs_file_truncate(lfs_t *lfs, lfs_file_t *file, lfs_off_t size);
#endif
// Return the position of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_CUR)
// Returns the position of the file, or a negative error code on failure.
lfs_soff_t lfs_file_tell(lfs_t *lfs, lfs_file_t *file);
// Change the position of the file to the beginning of the file
//
// Equivalent to lfs_file_seek(lfs, file, 0, LFS_SEEK_SET)
// Returns a negative error code on failure.
int lfs_file_rewind(lfs_t *lfs, lfs_file_t *file);
// Return the size of the file
//
// Similar to lfs_file_seek(lfs, file, 0, LFS_SEEK_END)
// Returns the size of the file, or a negative error code on failure.
lfs_soff_t lfs_file_size(lfs_t *lfs, lfs_file_t *file);
/// Directory operations ///
#ifndef LFS_READONLY
// Create a directory
//
// Returns a negative error code on failure.
int lfs_mkdir(lfs_t *lfs, const char *path);
#endif
// Open a directory
//
// Once open a directory can be used with read to iterate over files.
// Returns a negative error code on failure.
int lfs_dir_open(lfs_t *lfs, lfs_dir_t *dir, const char *path);
// Close a directory
//
// Releases any allocated resources.
// Returns a negative error code on failure.
int lfs_dir_close(lfs_t *lfs, lfs_dir_t *dir);
// Read an entry in the directory
//
// Fills out the info structure, based on the specified file or directory.
// Returns a positive value on success, 0 at the end of directory,
// or a negative error code on failure.
int lfs_dir_read(lfs_t *lfs, lfs_dir_t *dir, struct lfs_info *info);
// Change the position of the directory
//
// The new off must be a value previous returned from tell and specifies
// an absolute offset in the directory seek.
//
// Returns a negative error code on failure.
int lfs_dir_seek(lfs_t *lfs, lfs_dir_t *dir, lfs_off_t off);
// Return the position of the directory
//
// The returned offset is only meant to be consumed by seek and may not make
// sense, but does indicate the current position in the directory iteration.
//
// Returns the position of the directory, or a negative error code on failure.
lfs_soff_t lfs_dir_tell(lfs_t *lfs, lfs_dir_t *dir);
// Change the position of the directory to the beginning of the directory
//
// Returns a negative error code on failure.
int lfs_dir_rewind(lfs_t *lfs, lfs_dir_t *dir);
/// Filesystem-level filesystem operations
// Find on-disk info about the filesystem
//
// Fills out the fsinfo structure based on the filesystem found on-disk.
// Returns a negative error code on failure.
int lfs_fs_stat(lfs_t *lfs, struct lfs_fsinfo *fsinfo);
// Finds the current size of the filesystem
//
// Note: Result is best effort. If files share COW structures, the returned
// size may be larger than the filesystem actually is.
//
// Returns the number of allocated blocks, or a negative error code on failure.
lfs_ssize_t lfs_fs_size(lfs_t *lfs);
// Traverse through all blocks in use by the filesystem
//
// The provided callback will be called with each block address that is
// currently in use by the filesystem. This can be used to determine which
// blocks are in use or how much of the storage is available.
//
// Returns a negative error code on failure.
int lfs_fs_traverse(lfs_t *lfs, int (*cb)(void*, lfs_block_t), void *data);
#ifndef LFS_READONLY
// Attempt to make the filesystem consistent and ready for writing
//
// Calling this function is not required, consistency will be implicitly
// enforced on the first operation that writes to the filesystem, but this
// function allows the work to be performed earlier and without other
// filesystem changes.
//
// Returns a negative error code on failure.
int lfs_fs_mkconsistent(lfs_t *lfs);
#endif
#ifndef LFS_READONLY
// Attempt any janitorial work
//
// This currently:
// 1. Calls mkconsistent if not already consistent
// 2. Compacts metadata > compact_thresh
// 3. Populates the block allocator
//
// Though additional janitorial work may be added in the future.
//
// Calling this function is not required, but may allow the offloading of
// expensive janitorial work to a less time-critical code path.
//
// Returns a negative error code on failure. Accomplishing nothing is not
// an error.
int lfs_fs_gc(lfs_t *lfs);
#endif
#ifndef LFS_READONLY
// Grows the filesystem to a new size, updating the superblock with the new
// block count.
//
// If LFS_SHRINKNONRELOCATING is defined, this function will also accept
// block_counts smaller than the current configuration, after checking
// that none of the blocks that are being removed are in use.
// Note that littlefs's pseudorandom block allocation means that
// this is very unlikely to work in the general case.
//
// Returns a negative error code on failure.
int lfs_fs_grow(lfs_t *lfs, lfs_size_t block_count);
#endif
#ifndef LFS_READONLY
#ifdef LFS_MIGRATE
// Attempts to migrate a previous version of littlefs
//
// Behaves similarly to the lfs_format function. Attempts to mount
// the previous version of littlefs and update the filesystem so it can be
// mounted with the current version of littlefs.
//
// Requires a littlefs object and config struct. This clobbers the littlefs
// object, and does not leave the filesystem mounted. The config struct must
// be zeroed for defaults and backwards compatibility.
//
// Returns a negative error code on failure.
int lfs_migrate(lfs_t *lfs, const struct lfs_config *cfg);
#endif
#endif
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
-37
View File
@@ -1,37 +0,0 @@
/*
* lfs util functions
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "lfs_util.h"
// Only compile if user does not provide custom config
#ifndef LFS_CONFIG
// If user provides their own CRC impl we don't need this
#ifndef LFS_CRC
// Software CRC implementation with small lookup table
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
static const uint32_t rtable[16] = {
0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac,
0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c,
0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c,
0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c,
};
const uint8_t *data = buffer;
for (size_t i = 0; i < size; i++) {
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf];
crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf];
}
return crc;
}
#endif
#endif
-277
View File
@@ -1,277 +0,0 @@
/*
* lfs utility functions
*
* Copyright (c) 2022, The littlefs authors.
* Copyright (c) 2017, Arm Limited. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef LFS_UTIL_H
#define LFS_UTIL_H
#define LFS_STRINGIZE(x) LFS_STRINGIZE2(x)
#define LFS_STRINGIZE2(x) #x
// Users can override lfs_util.h with their own configuration by defining
// LFS_CONFIG as a header file to include (-DLFS_CONFIG=lfs_config.h).
//
// If LFS_CONFIG is used, none of the default utils will be emitted and must be
// provided by the config file. To start, I would suggest copying lfs_util.h
// and modifying as needed.
#ifdef LFS_CONFIG
#include LFS_STRINGIZE(LFS_CONFIG)
#else
// Alternatively, users can provide a header file which defines
// macros and other things consumed by littlefs.
//
// For example, provide my_defines.h, which contains
// something like:
//
// #include <stddef.h>
// extern void *my_malloc(size_t sz);
// #define LFS_MALLOC(sz) my_malloc(sz)
//
// And build littlefs with the header by defining LFS_DEFINES.
// (-DLFS_DEFINES=my_defines.h)
#ifdef LFS_DEFINES
#include LFS_STRINGIZE(LFS_DEFINES)
#endif
// System includes
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <inttypes.h>
#define LFS_NO_DEBUG
#define LFS_NO_WARN
#define LFS_NO_ERROR
#ifndef LFS_NO_MALLOC
#include <quakey.h>
#endif
#ifndef LFS_NO_ASSERT
#include <assert.h>
#endif
#if !defined(LFS_NO_DEBUG) || \
!defined(LFS_NO_WARN) || \
!defined(LFS_NO_ERROR) || \
defined(LFS_YES_TRACE)
#include <stdio.h>
#endif
#ifdef __cplusplus
extern "C"
{
#endif
// Macros, may be replaced by system specific wrappers. Arguments to these
// macros must not have side-effects as the macros can be removed for a smaller
// code footprint
// Logging functions
#ifndef LFS_TRACE
#ifdef LFS_YES_TRACE
#define LFS_TRACE_(fmt, ...) \
printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "")
#else
#define LFS_TRACE(...)
#endif
#endif
#ifndef LFS_DEBUG
#ifndef LFS_NO_DEBUG
#define LFS_DEBUG_(fmt, ...) \
printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "")
#else
#define LFS_DEBUG(...)
#endif
#endif
#ifndef LFS_WARN
#ifndef LFS_NO_WARN
#define LFS_WARN_(fmt, ...) \
printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "")
#else
#define LFS_WARN(...)
#endif
#endif
#ifndef LFS_ERROR
#ifndef LFS_NO_ERROR
#define LFS_ERROR_(fmt, ...) \
printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__)
#define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "")
#else
#define LFS_ERROR(...)
#endif
#endif
// Runtime assertions
#ifndef LFS_ASSERT
#ifndef LFS_NO_ASSERT
#define LFS_ASSERT(test) assert(test)
#else
#define LFS_ASSERT(test)
#endif
#endif
// Builtin functions, these may be replaced by more efficient
// toolchain-specific implementations. LFS_NO_INTRINSICS falls back to a more
// expensive basic C implementation for debugging purposes
// Min/max functions for unsigned 32-bit numbers
static inline uint32_t lfs_max(uint32_t a, uint32_t b) {
return (a > b) ? a : b;
}
static inline uint32_t lfs_min(uint32_t a, uint32_t b) {
return (a < b) ? a : b;
}
// Align to nearest multiple of a size
static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) {
return a - (a % alignment);
}
static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) {
return lfs_aligndown(a + alignment-1, alignment);
}
// Find the smallest power of 2 greater than or equal to a
static inline uint32_t lfs_npw2(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return 32 - __builtin_clz(a-1);
#else
uint32_t r = 0;
uint32_t s;
a -= 1;
s = (a > 0xffff) << 4; a >>= s; r |= s;
s = (a > 0xff ) << 3; a >>= s; r |= s;
s = (a > 0xf ) << 2; a >>= s; r |= s;
s = (a > 0x3 ) << 1; a >>= s; r |= s;
return (r | (a >> 1)) + 1;
#endif
}
// Count the number of trailing binary zeros in a
// lfs_ctz(0) may be undefined
static inline uint32_t lfs_ctz(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && defined(__GNUC__)
return __builtin_ctz(a);
#else
return lfs_npw2((a & -a) + 1) - 1;
#endif
}
// Count the number of binary ones in a
static inline uint32_t lfs_popc(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM))
return __builtin_popcount(a);
#else
a = a - ((a >> 1) & 0x55555555);
a = (a & 0x33333333) + ((a >> 2) & 0x33333333);
return (((a + (a >> 4)) & 0xf0f0f0f) * 0x1010101) >> 24;
#endif
}
// Find the sequence comparison of a and b, this is the distance
// between a and b ignoring overflow
static inline int lfs_scmp(uint32_t a, uint32_t b) {
return (int)(unsigned)(a - b);
}
// Convert between 32-bit little-endian and native order
static inline uint32_t lfs_fromle32(uint32_t a) {
#if (defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
return a;
#elif !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__))
return __builtin_bswap32(a);
#else
return ((uint32_t)((uint8_t*)&a)[0] << 0) |
((uint32_t)((uint8_t*)&a)[1] << 8) |
((uint32_t)((uint8_t*)&a)[2] << 16) |
((uint32_t)((uint8_t*)&a)[3] << 24);
#endif
}
static inline uint32_t lfs_tole32(uint32_t a) {
return lfs_fromle32(a);
}
// Convert between 32-bit big-endian and native order
static inline uint32_t lfs_frombe32(uint32_t a) {
#if !defined(LFS_NO_INTRINSICS) && ( \
(defined( BYTE_ORDER ) && defined( ORDER_LITTLE_ENDIAN ) && BYTE_ORDER == ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_LITTLE_ENDIAN ) && __BYTE_ORDER == __ORDER_LITTLE_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_LITTLE_ENDIAN__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__))
return __builtin_bswap32(a);
#elif (defined( BYTE_ORDER ) && defined( ORDER_BIG_ENDIAN ) && BYTE_ORDER == ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER ) && defined(__ORDER_BIG_ENDIAN ) && __BYTE_ORDER == __ORDER_BIG_ENDIAN ) || \
(defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
return a;
#else
return ((uint32_t)((uint8_t*)&a)[0] << 24) |
((uint32_t)((uint8_t*)&a)[1] << 16) |
((uint32_t)((uint8_t*)&a)[2] << 8) |
((uint32_t)((uint8_t*)&a)[3] << 0);
#endif
}
static inline uint32_t lfs_tobe32(uint32_t a) {
return lfs_frombe32(a);
}
// Calculate CRC-32 with polynomial = 0x04c11db7
#ifdef LFS_CRC
static inline uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) {
return LFS_CRC(crc, buffer, size);
}
#else
uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size);
#endif
// Allocate memory, only used if buffers are not provided to littlefs
//
// littlefs current has no alignment requirements, as it only allocates
// byte-level buffers.
static inline void *lfs_malloc(size_t size) {
#if defined(LFS_MALLOC)
return LFS_MALLOC(size);
#elif !defined(LFS_NO_MALLOC)
return malloc(size);
#else
(void)size;
return NULL;
#endif
}
// Deallocate memory, only used if buffers are not provided to littlefs
static inline void lfs_free(void *p) {
#if defined(LFS_FREE)
LFS_FREE(p);
#elif !defined(LFS_NO_MALLOC)
free(p);
#else
(void)p;
#endif
}
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif
#endif
+928
View File
@@ -0,0 +1,928 @@
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include "mockfs.h"
typedef struct {
char *ptr;
int len;
} Slice;
#define S(X) (Slice) { (X), (int) sizeof(X)-1 }
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
#define MOCKFS_COMP_LIMIT 32
struct MockFS_Entity {
MockFS_Entity *parent;
MockFS_Entity *prev;
MockFS_Entity *next;
char name[MOCKFS_NAME_SIZE];
int name_len;
bool is_dir;
int refcount; // Number of open file handles pointing to this entity
union {
struct {
MockFS_Entity *head_child;
MockFS_Entity *tail_child;
};
ByteBuffer byte_buffer;
};
};
static bool slice_eq(Slice s1, Slice s2)
{
if (s1.len != s2.len)
return false;
return !memcmp(s1.ptr, s2.ptr, s1.len);
}
static void *alloc(MockFS *mfs, int len, int align)
{
int pad = -(unsigned long long) (mfs->mem + mfs->off) & (align - 1);
if (mfs->len - mfs->off < pad + len)
return NULL;
void *p = mfs->mem + mfs->off + pad;
mfs->off += pad + len;
return p;
}
static void byte_buffer_init(ByteBuffer *byte_buffer)
{
byte_buffer->used = 0;
byte_buffer->tail_used = 0;
byte_buffer->head = NULL;
byte_buffer->tail = NULL;
}
static void byte_buffer_free(ByteBuffer *byte_buffer, ByteChunk **free_list)
{
if (byte_buffer->head) {
byte_buffer->tail->next = *free_list;
*free_list = byte_buffer->head;
}
}
static int convert_offset(ByteBuffer *byte_buffer, int off, ByteChunk **pchunk, int *poffset)
{
assert(off > -1);
int skipped = 0;
ByteChunk *chunk = byte_buffer->head;
while (chunk) {
int chunk_used = (chunk->next ? BYTE_CHUNK_SIZE : byte_buffer->tail_used);
if (off >= skipped && off < skipped + chunk_used) {
*pchunk = chunk;
*poffset = off - skipped;
return 1;
}
chunk = chunk->next;
skipped += chunk_used;
}
if (off == skipped) {
*pchunk = byte_buffer->tail;
*poffset = byte_buffer->tail ? byte_buffer->tail_used : 0;
return 1;
}
return 0;
}
static int byte_buffer_read(ByteBuffer *byte_buffer, int off, char *dst, int cap)
{
int rel_off;
ByteChunk *chunk;
convert_offset(byte_buffer, off, &chunk, &rel_off);
int copied = 0;
while (copied < cap) {
if (chunk == byte_buffer->tail) {
if (rel_off == byte_buffer->tail_used)
break;
int cpy = MIN(byte_buffer->tail_used - rel_off, cap - copied);
memcpy(dst + copied, chunk->data + rel_off, cpy);
copied += cpy;
break; // No more chunks after tail
} else {
int cpy = MIN(BYTE_CHUNK_SIZE - rel_off, cap - copied);
memcpy(dst + copied, chunk->data + rel_off, cpy);
copied += cpy;
chunk = chunk->next;
rel_off = 0;
}
}
return copied;
}
// Calculate total used bytes in the buffer
static int byte_buffer_size(ByteBuffer *byte_buffer)
{
int size = 0;
ByteChunk *chunk = byte_buffer->head;
while (chunk) {
if (chunk->next) {
size += BYTE_CHUNK_SIZE;
} else {
size += byte_buffer->tail_used;
}
chunk = chunk->next;
}
return size;
}
// Extend buffer to given size by filling with zeros
static int byte_buffer_extend(ByteBuffer *byte_buffer, int target_size, MockFS *mfs)
{
int current_size = byte_buffer_size(byte_buffer);
int to_write = target_size - current_size;
while (to_write > 0) {
// Get or create tail chunk
if (byte_buffer->tail == NULL || byte_buffer->tail_used == BYTE_CHUNK_SIZE) {
ByteChunk *tmp = mfs->chunk_free_list;
if (tmp == NULL) {
tmp = alloc(mfs, sizeof(ByteChunk), _Alignof(ByteChunk));
if (tmp == NULL)
return MOCKFS_ERRNO_NOSPC;
} else {
mfs->chunk_free_list = tmp->next;
}
tmp->next = NULL;
if (byte_buffer->head == NULL) {
byte_buffer->head = tmp;
} else {
byte_buffer->tail->next = tmp;
}
byte_buffer->tail = tmp;
byte_buffer->tail_used = 0;
}
// Fill remaining space in tail chunk with zeros
int space = BYTE_CHUNK_SIZE - byte_buffer->tail_used;
int fill = MIN(space, to_write);
memset(byte_buffer->tail->data + byte_buffer->tail_used, 0, fill);
byte_buffer->tail_used += fill;
to_write -= fill;
}
return 0;
}
static int byte_buffer_write(ByteBuffer *byte_buffer, int off, char *src, int len, MockFS *mfs)
{
int rel_off;
ByteChunk *chunk;
if (!convert_offset(byte_buffer, off, &chunk, &rel_off)) {
// Offset is beyond end of buffer - extend with zeros
int ret = byte_buffer_extend(byte_buffer, off, mfs);
if (ret < 0)
return ret;
convert_offset(byte_buffer, off, &chunk, &rel_off);
}
int copied = 0;
while (copied < len) {
if (chunk == byte_buffer->tail) {
if (chunk == NULL || rel_off == BYTE_CHUNK_SIZE) {
ByteChunk *tmp = mfs->chunk_free_list;
if (tmp == NULL) {
tmp = alloc(mfs, sizeof(ByteChunk), _Alignof(ByteChunk));
if (tmp == NULL)
return MOCKFS_ERRNO_NOSPC;
} else {
mfs->chunk_free_list = tmp->next;
}
tmp->next = NULL;
if (byte_buffer->head == NULL) {
byte_buffer->head = tmp;
} else {
byte_buffer->tail->next = tmp;
}
byte_buffer->tail = tmp;
byte_buffer->tail_used = 0;
rel_off = 0;
int cpy = MIN(BYTE_CHUNK_SIZE, len - copied);
assert(cpy > 0);
memcpy(tmp->data, src + copied, cpy);
copied += cpy;
chunk = tmp;
rel_off += cpy;
byte_buffer->tail_used = cpy;
} else if (rel_off == byte_buffer->tail_used) {
assert(chunk);
int cpy = MIN(BYTE_CHUNK_SIZE - byte_buffer->tail_used, len - copied);
assert(cpy > 0);
memcpy(chunk->data + byte_buffer->tail_used, src + copied, cpy);
copied += cpy;
rel_off += cpy;
byte_buffer->tail_used += cpy;
} else {
assert(rel_off < byte_buffer->tail_used);
assert(chunk);
int cpy = MIN(byte_buffer->tail_used - rel_off, len - copied);
assert(cpy > 0);
memcpy(chunk->data + rel_off, src + copied, cpy);
copied += cpy;
rel_off += cpy;
}
} else {
assert(chunk);
int cpy = MIN(BYTE_CHUNK_SIZE - rel_off, len - copied);
assert(cpy > 0);
memcpy(chunk->data + rel_off, src + copied, cpy);
copied += cpy;
chunk = chunk->next;
rel_off = 0;
}
}
return 0;
}
static int parse_path(char *src, int len, Slice *buf, int cap)
{
int cur = 0;
int ret = 0;
if (len > 0 && src[0] == '/')
cur++;
for (;;) {
int off = cur;
while (cur < len && src[cur] != '/')
cur++;
Slice s = { src + off, cur - off };
if (s.len > 0) {
if (ret == cap)
return -1; // TODO: proper error code
buf[ret++] = s;
}
if (cur == len)
break;
assert(src[cur] == '/');
cur++;
}
return ret;
}
static int resolve_path(MockFS *mfs, Slice *comps, int num_comps,
MockFS_Entity **stack, int cap)
{
int ret = 0;
stack[ret++] = mfs->root;
for (int i = 0; i < num_comps; i++) {
if (!stack[ret-1]->is_dir)
return MOCKFS_ERRNO_NOTDIR;
if (slice_eq(comps[i], S(".."))) {
ret--;
continue;
}
if (slice_eq(comps[i], S(".")))
continue;
MockFS_Entity *child = stack[ret-1]->head_child;
while (child) {
if (slice_eq(comps[i], (Slice) { child->name, child->name_len }))
break;
child = child->next;
}
if (child == NULL)
return MOCKFS_ERRNO_NOENT;
if (ret == cap)
return -1; // TODO: return proper error code
stack[ret++] = child;
}
return ret;
}
static int entity_init(MockFS_Entity *entity, Slice name, bool is_dir)
{
entity->parent = NULL;
entity->prev = NULL;
entity->next = NULL;
entity->is_dir = is_dir;
entity->refcount = 0;
entity->head_child = NULL;
entity->tail_child = NULL;
if (name.len > (int) sizeof(entity->name))
return -1; // TODO: proper error code
memcpy(entity->name, name.ptr, name.len);
entity->name_len = name.len;
return 0;
}
int mockfs_init(MockFS **pmfs, char *mem, int len)
{
int off = -(unsigned long long) mem & (_Alignof(MockFS)-1);
if (off + sizeof(MockFS) > (unsigned long long)len)
return MOCKFS_ERRNO_NOMEM;
MockFS *mfs = (MockFS *)(mem + off);
mfs->mem = mem;
mfs->len = len;
mfs->off = off + sizeof(MockFS);
mfs->root = NULL;
mfs->entity_free_list = NULL;
mfs->chunk_free_list = NULL;
MockFS_Entity *entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
if (entity == NULL)
return MOCKFS_ERRNO_NOMEM;
entity_init(entity, (Slice) { "", 0 }, true);
mfs->root = entity;
*pmfs = mfs;
return 0;
}
void mockfs_free(MockFS *mfs)
{
(void) mfs;
}
static bool find_child(MockFS_Entity *entity, Slice child_name)
{
assert(entity->is_dir);
MockFS_Entity *child = entity->head_child;
while (child) {
if (slice_eq(child_name, (Slice) { child->name, child->name_len }))
return true;
child = child->next;
}
return false;
}
static int create_entity(MockFS *mfs, MockFS_Entity *parent, Slice name, bool is_dir)
{
MockFS_Entity *entity = mfs->entity_free_list;
if (entity == NULL) {
entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
if (entity == NULL)
return MOCKFS_ERRNO_NOMEM;
} else {
mfs->entity_free_list = entity->next;
}
entity->next = NULL;
int ret = entity_init(entity, name, is_dir);
if (ret < 0) {
entity->next = mfs->entity_free_list;
mfs->entity_free_list = entity;
return ret;
}
// Initialize file-specific fields
byte_buffer_init(&entity->byte_buffer);
// Link to parent directory
entity->parent = parent;
entity->next = NULL;
entity->prev = parent->tail_child;
if (parent->tail_child) {
parent->tail_child->next = entity;
} else {
parent->head_child = entity;
}
parent->tail_child = entity;
return 0;
}
int mockfs_open(MockFS *mfs, char *path, int path_len, int flags, MockFS_OpenFile *open_file)
{
Slice comps[MOCKFS_COMP_LIMIT];
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
int num_comps = ret;
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
bool file_was_created = false;
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
if (ret < 0) {
// If file doesn't exist AND O_CREAT is specified, try to create it
if (ret == MOCKFS_ERRNO_NOENT && (flags & MOCKFS_O_CREAT)) {
// Resolve parent directory
ret = resolve_path(mfs, comps, num_comps-1, stack, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
assert(ret > 0);
MockFS_Entity *parent = stack[ret-1];
// Path ending with '/' implies directory, but you can't create
// a directory with open() - that's what mkdir() is for
if (path_len > 1 && path[path_len-1] == '/') {
return MOCKFS_ERRNO_ISDIR;
}
// Create the file
ret = create_entity(mfs, parent, comps[num_comps-1], false);
if (ret < 0)
return ret;
file_was_created = true;
// Retry full path resolution (now should succeed)
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
assert(ret > 0);
} else {
return ret;
}
}
assert(ret > 0);
// Check for trailing slash (but "/" alone is not a "trailing slash" case)
bool has_trailing_slash = (path_len > 1 && path[path_len-1] == '/');
if ((flags & MOCKFS_O_CREAT) && has_trailing_slash)
return MOCKFS_ERRNO_ISDIR;
// O_EXCL check: if file already existed and O_EXCL is set, fail
if ((flags & MOCKFS_O_EXCL) && (flags & MOCKFS_O_CREAT) && !file_was_created)
return MOCKFS_ERRNO_EXIST;
if (stack[ret-1]->is_dir)
return MOCKFS_ERRNO_ISDIR;
if (has_trailing_slash) {
// If target is not a directory, return ENOTDIR
if (!stack[ret-1]->is_dir)
return MOCKFS_ERRNO_NOTDIR;
}
// O_TRUNC: truncate file to zero length if opened for writing
if ((flags & MOCKFS_O_TRUNC) && (flags & (MOCKFS_O_WRONLY | MOCKFS_O_RDWR))) {
byte_buffer_free(&stack[ret-1]->byte_buffer, &mfs->chunk_free_list);
byte_buffer_init(&stack[ret-1]->byte_buffer);
}
open_file->mfs = mfs;
open_file->entity = stack[ret-1];
open_file->offset = 0;
open_file->flags = flags;
open_file->entity->refcount++;
return 0;
}
int mockfs_open_dir(MockFS *mfs, char *path, int path_len, MockFS_OpenDir *open_dir)
{
Slice comps[MOCKFS_COMP_LIMIT];
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
int num_comps = ret;
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
assert(ret > 0);
if (!stack[ret-1]->is_dir)
return MOCKFS_ERRNO_NOTDIR;
open_dir->mfs = mfs;
open_dir->entity = stack[ret-1];
open_dir->child = stack[ret-1]->head_child;
open_dir->idx = 0;
return 0;
}
int mockfs_file_size(MockFS_OpenFile *open_file)
{
return byte_buffer_size(&open_file->entity->byte_buffer);
}
void mockfs_close_file(MockFS_OpenFile *open_file)
{
MockFS_Entity *entity = open_file->entity;
entity->refcount--;
// If refcount drops to 0 and entity was unlinked (removed while open),
// now we can actually free it
if (entity->refcount == 0 && entity->parent == NULL) {
// Free the byte buffer chunks
byte_buffer_free(&entity->byte_buffer, &open_file->mfs->chunk_free_list);
// Add entity to free list
entity->next = open_file->mfs->entity_free_list;
open_file->mfs->entity_free_list = entity;
}
}
void mockfs_close_dir(MockFS_OpenDir *open_dir)
{
(void) open_dir;
}
int mockfs_read(MockFS_OpenFile *open_file, char *dst, int len)
{
if (open_file->flags & MOCKFS_O_WRONLY) {
return MOCKFS_ERRNO_BADF;
}
int copied = byte_buffer_read(&open_file->entity->byte_buffer, open_file->offset, dst, len);
open_file->offset += copied;
return copied;
}
int mockfs_write(MockFS_OpenFile *open_file, char *src, int len)
{
if (!(open_file->flags & (MOCKFS_O_WRONLY | MOCKFS_O_RDWR))) {
return MOCKFS_ERRNO_BADF;
}
if ((open_file->flags & MOCKFS_O_WRONLY) && (open_file->flags & MOCKFS_O_RDWR)) {
return MOCKFS_ERRNO_BADF;
}
// If O_APPEND is set, seek to end before writing
if (open_file->flags & MOCKFS_O_APPEND) {
open_file->offset = byte_buffer_size(&open_file->entity->byte_buffer);
}
int ret = byte_buffer_write(&open_file->entity->byte_buffer, open_file->offset, src, len, open_file->mfs);
if (ret < 0)
return ret;
open_file->offset += len;
return len;
}
int mockfs_read_dir(MockFS_OpenDir *open_dir, MockFS_Dirent *dirent)
{
if (open_dir->idx == 0) {
if (sizeof(dirent->name) < 1)
return -1; // TODO: proper error code
dirent->name[0] = '.';
dirent->name_len = 1;
dirent->is_dir = true;
open_dir->idx++;
return 0;
}
if (open_dir->idx == 1) {
if (sizeof(dirent->name) < 2)
return -1; // TODO: proper error code
dirent->name[0] = '.';
dirent->name[1] = '.';
dirent->name_len = 2;
dirent->is_dir = true;
open_dir->idx++;
return 0;
}
if (open_dir->child == NULL)
return MOCKFS_ERRNO_NOENT;
memcpy(dirent->name, open_dir->child->name, open_dir->child->name_len);
dirent->name_len = open_dir->child->name_len;
dirent->is_dir = open_dir->child->is_dir;
open_dir->child = open_dir->child->next;
open_dir->idx++;
return 0;
}
int mockfs_sync(MockFS_OpenFile *open_file)
{
// TODO
(void) open_file;
return 0;
}
int mockfs_lseek(MockFS_OpenFile *open_file, int offset, int whence)
{
int new_offset;
switch (whence) {
case MOCKFS_SEEK_SET:
new_offset = offset;
break;
case MOCKFS_SEEK_CUR:
new_offset = open_file->offset + offset;
break;
case MOCKFS_SEEK_END:
new_offset = byte_buffer_size(&open_file->entity->byte_buffer) + offset;
break;
default:
return MOCKFS_ERRNO_INVAL;
}
if (new_offset < 0)
return MOCKFS_ERRNO_INVAL;
open_file->offset = new_offset;
return new_offset;
}
static int remove_inner(MockFS *mfs, MockFS_Entity *entity, bool recursive)
{
if (entity->parent == NULL)
return MOCKFS_ERRNO_BUSY;
if (entity->is_dir) {
// Remove children
if (entity->head_child) {
if (!recursive)
return MOCKFS_ERRNO_NOTEMPTY;
MockFS_Entity *child = entity->head_child;
while (child) {
MockFS_Entity *next = child->next;
remove_inner(mfs, child, true);
child = next;
}
}
}
// Unlink entity node from parent
if (entity->prev) {
entity->prev->next = entity->next;
} else {
entity->parent->head_child = entity->next;
}
if (entity->next) {
entity->next->prev = entity->prev;
} else {
entity->parent->tail_child = entity->prev;
}
// Mark as unlinked
entity->parent = NULL;
// Only fully free the entity if no open handles
if (entity->refcount == 0) {
if (!entity->is_dir) {
// Append chunks to the free list
byte_buffer_free(&entity->byte_buffer, &mfs->chunk_free_list);
}
// Append entity to the free list
entity->next = mfs->entity_free_list;
mfs->entity_free_list = entity;
}
return 0;
}
int mockfs_remove(MockFS *mfs, char *path, int path_len, bool recursive)
{
Slice comps[MOCKFS_COMP_LIMIT];
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
int num_comps = ret;
if (num_comps > 0) {
if (slice_eq(comps[num_comps-1], S(".")))
return MOCKFS_ERRNO_INVAL;
if (slice_eq(comps[num_comps-1], S("..")))
return MOCKFS_ERRNO_INVAL;
}
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
ret = resolve_path(mfs, comps, num_comps, stack, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
assert(ret > 0);
if (path_len > 0 && path[path_len-1] == '/') {
if (!stack[ret-1]->is_dir)
return MOCKFS_ERRNO_NOTDIR;
}
return remove_inner(mfs, stack[ret-1], recursive);
}
int mockfs_mkdir(MockFS *mfs, char *path, int path_len)
{
Slice comps[MOCKFS_COMP_LIMIT];
int ret = parse_path(path, path_len, comps, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
if (ret == 0)
return MOCKFS_ERRNO_EXIST;
int num_comps = ret;
if (slice_eq(comps[num_comps-1], S(".")))
return MOCKFS_ERRNO_EXIST;
if (slice_eq(comps[num_comps-1], S("..")))
return MOCKFS_ERRNO_INVAL;
MockFS_Entity *stack[MOCKFS_COMP_LIMIT];
ret = resolve_path(mfs, comps, num_comps-1, stack, MOCKFS_COMP_LIMIT);
if (ret < 0)
return ret;
assert(ret > 0);
MockFS_Entity *parent = stack[ret-1];
if (!parent->is_dir)
return MOCKFS_ERRNO_NOTDIR;
if (find_child(parent, comps[num_comps-1]))
return MOCKFS_ERRNO_EXIST;
MockFS_Entity *entity = mfs->entity_free_list;
if (entity == NULL) {
entity = alloc(mfs, sizeof(MockFS_Entity), _Alignof(MockFS_Entity));
if (entity == NULL)
return MOCKFS_ERRNO_NOMEM;
} else {
mfs->entity_free_list = entity->next;
}
entity->next = NULL;
ret = entity_init(entity, comps[num_comps-1], true);
if (ret < 0) {
entity->next = mfs->entity_free_list;
mfs->entity_free_list = entity;
return ret;
}
// Initialize byte_buffer to clear all 24 bytes of the union, including
// byte_buffer.tail (bytes 16-23) which would otherwise retain garbage
// when reusing an entity from the free list
byte_buffer_init(&entity->byte_buffer);
entity->parent = parent;
entity->next = NULL;
entity->prev = parent->tail_child;
if (parent->tail_child) {
parent->tail_child->next = entity;
} else {
parent->head_child = entity;
}
parent->tail_child = entity;
return 0;
}
int mockfs_rename(MockFS *mfs, char *old_path, int old_path_len, char *new_path, int new_path_len)
{
Slice new_comps[MOCKFS_COMP_LIMIT];
Slice old_comps[MOCKFS_COMP_LIMIT];
int num_new_comps = parse_path(new_path, new_path_len, new_comps, MOCKFS_COMP_LIMIT);
int num_old_comps = parse_path(old_path, old_path_len, old_comps, MOCKFS_COMP_LIMIT);
if (num_new_comps < 0) return num_new_comps;
if (num_old_comps < 0) return num_old_comps;
MockFS_Entity *new_stack[MOCKFS_COMP_LIMIT];
MockFS_Entity *old_stack[MOCKFS_COMP_LIMIT];
int num_new_stack = resolve_path(mfs, new_comps, num_new_comps-1, new_stack, MOCKFS_COMP_LIMIT);
int num_old_stack = resolve_path(mfs, old_comps, num_old_comps, old_stack, MOCKFS_COMP_LIMIT);
if (num_new_stack == MOCKFS_ERRNO_NOTDIR ||
num_old_stack == MOCKFS_ERRNO_NOTDIR)
return MOCKFS_ERRNO_NOTDIR;
if (num_new_stack < 0)
return num_new_stack;
assert(num_new_stack > 0);
if (!new_stack[num_new_stack-1]->is_dir)
return MOCKFS_ERRNO_NOTDIR;
if (num_old_stack < 0)
return num_old_stack;
assert(num_old_stack > 0);
MockFS_Entity *source = old_stack[num_old_stack-1];
if (source->parent == NULL)
return MOCKFS_ERRNO_BUSY;
if (old_path_len > 0 && old_path[old_path_len-1] == '/') {
if (!source->is_dir)
return MOCKFS_ERRNO_NOTDIR;
}
if (new_path_len > 0 && new_path[new_path_len-1] == '/') {
if (!source->is_dir)
return MOCKFS_ERRNO_NOTDIR;
}
if (num_new_comps == 0)
return MOCKFS_ERRNO_BUSY;
// Make sure the entity isn't being moved inside itself,
// by checking that the last element of the old stack isn't
// in the new stack.
for (int i = 0; i < num_new_stack; i++)
if (new_stack[i] == source)
return MOCKFS_ERRNO_INVAL;
// Check if new path exists
Slice new_name = new_comps[num_new_comps-1];
MockFS_Entity *target = NULL;
MockFS_Entity *child = new_stack[num_new_stack-1]->head_child;
while (child) {
if (slice_eq(new_name, (Slice) { child->name, child->name_len })) {
target = child;
break;
}
child = child->next;
}
if (target) {
if (target == source)
return 0;
if (target->is_dir) {
for (int i = 0; i < num_old_stack; i++)
if (old_stack[i] == target)
return MOCKFS_ERRNO_NOTEMPTY;
if (!source->is_dir)
return MOCKFS_ERRNO_ISDIR;
if (target->head_child)
return MOCKFS_ERRNO_NOTEMPTY;
} else {
if (source->is_dir)
return MOCKFS_ERRNO_NOTDIR;
}
remove_inner(mfs, target, false);
}
// Unlink source from old parent
if (source->prev) {
source->prev->next = source->next;
} else {
source->parent->head_child = source->next;
}
if (source->next) {
source->next->prev = source->prev;
} else {
source->parent->tail_child = source->prev;
}
// Update source's name
if (new_name.len > (int) sizeof(source->name))
return MOCKFS_ERRNO_INVAL; // Name too long
memcpy(source->name, new_name.ptr, new_name.len);
source->name_len = new_name.len;
// Link source to new parent
source->parent = new_stack[num_new_stack-1];
source->next = NULL;
source->prev = new_stack[num_new_stack-1]->tail_child;
if (new_stack[num_new_stack-1]->tail_child) {
new_stack[num_new_stack-1]->tail_child->next = source;
} else {
new_stack[num_new_stack-1]->head_child = source;
}
new_stack[num_new_stack-1]->tail_child = source;
return 0;
}
+109
View File
@@ -0,0 +1,109 @@
#ifndef MOCKFS_INCLUDED
#define MOCKFS_INCLUDED
#include <stdbool.h>
#define MOCKFS_NAME_SIZE (1<<7)
enum {
MOCKFS_ERRNO_SUCCESS = 0,
MOCKFS_ERRNO_NOENT = -1, // No such file or directory
MOCKFS_ERRNO_PERM = -2, // Operation not permitted
MOCKFS_ERRNO_NOMEM = -3, // Out of memory
MOCKFS_ERRNO_NOTDIR = -4, // Not a directory
MOCKFS_ERRNO_ISDIR = -5, // Is a directory
MOCKFS_ERRNO_INVAL = -6, // Invalid argument
MOCKFS_ERRNO_NOTEMPTY = -7, // Directory not empty
MOCKFS_ERRNO_NOSPC = -8, // No space left on device
MOCKFS_ERRNO_EXIST = -9, // File exists
MOCKFS_ERRNO_BUSY = -10,
MOCKFS_ERRNO_BADF = -11,
};
enum {
MOCKFS_O_RDONLY = 0x00,
MOCKFS_O_WRONLY = 0x01,
MOCKFS_O_RDWR = 0x02,
MOCKFS_O_CREAT = 0x40,
MOCKFS_O_EXCL = 0x80,
MOCKFS_O_TRUNC = 0x200,
MOCKFS_O_APPEND = 0x400,
};
enum {
MOCKFS_SEEK_SET = 0,
MOCKFS_SEEK_CUR = 1,
MOCKFS_SEEK_END = 2,
};
#define BYTE_CHUNK_SIZE 128
typedef struct ByteChunk ByteChunk;
struct ByteChunk {
ByteChunk *next;
char data[BYTE_CHUNK_SIZE];
};
typedef struct {
int used;
int tail_used;
ByteChunk *head;
ByteChunk *tail;
} ByteBuffer;
typedef struct MockFS_Entity MockFS_Entity;
typedef struct {
char *mem;
int len;
int off;
MockFS_Entity *root;
MockFS_Entity *entity_free_list;
ByteChunk *chunk_free_list;
} MockFS;
typedef struct {
MockFS* mfs;
MockFS_Entity* entity;
int offset;
int flags;
} MockFS_OpenFile;
typedef struct {
MockFS* mfs;
MockFS_Entity* entity;
MockFS_Entity* child;
int idx;
} MockFS_OpenDir;
typedef struct {
char name[MOCKFS_NAME_SIZE];
int name_len;
bool is_dir;
} MockFS_Dirent;
int mockfs_init(MockFS **mfs, char *mem, int len);
void mockfs_free(MockFS *mfs);
int mockfs_open(MockFS *mfs, char *path, int path_len, int flags, MockFS_OpenFile *open_file);
int mockfs_open_dir(MockFS *mfs, char *path, int path_len, MockFS_OpenDir *open_dir);
int mockfs_file_size(MockFS_OpenFile *open_file);
void mockfs_close_file(MockFS_OpenFile *open_file);
void mockfs_close_dir(MockFS_OpenDir *open_dir);
int mockfs_read(MockFS_OpenFile *open_file, char *dst, int len);
int mockfs_write(MockFS_OpenFile *open_file, char *src, int len);
int mockfs_read_dir(MockFS_OpenDir *open_dir, MockFS_Dirent *dirent);
int mockfs_sync(MockFS_OpenFile *open_file);
int mockfs_lseek(MockFS_OpenFile *open_file, int offset, int whence);
int mockfs_remove(MockFS *mfs, char *path, int path_len, bool recursive);
int mockfs_mkdir(MockFS *mfs, char *path, int path_len);
int mockfs_rename(MockFS *mfs, char *old_path, int old_path_len, char *new_path, int new_path_len);
#endif // MOCKFS_INCLUDED
+88 -185
View File
@@ -1,8 +1,10 @@
/////////////////////////////////////////////////////////////////
// Includes
#include "lfs.h"
#include "mockfs.h"
#include <quakey.h>
#include <stdint.h>
#include <assert.h>
/////////////////////////////////////////////////////////////////
// Utilities
@@ -170,12 +172,12 @@ struct Desc {
/////////////////////////////////////////
// File fields
lfs_file_t file;
MockFS_OpenFile file;
/////////////////////////////////////////
// Directory fields
lfs_dir_t dir;
MockFS_OpenDir dir;
/////////////////////////////////////////
};
@@ -210,6 +212,11 @@ enum {
HOST_ERROR_NOTEMPTY = -18,
HOST_ERROR_EXIST = -19,
HOST_ERROR_EXISTS = -19, // Alias for HOST_ERROR_EXIST
HOST_ERROR_PERM = -20,
HOST_ERROR_NOTDIR = -21,
HOST_ERROR_NOSPC = -22,
HOST_ERROR_BUSY = -23,
HOST_ERROR_BADF = -24,
};
// lseek whence values for host_lseek
@@ -276,9 +283,8 @@ struct Host {
int disk_size;
char *disk_data;
// LittleFS instance managing the disk bytes
lfs_t lfs;
struct lfs_config lfs_cfg;
// MockFS instance managing the disk bytes
MockFS *mfs;
};
typedef struct {
@@ -767,7 +773,7 @@ static bool accept_queue_empty(AcceptQueue *queue)
// If the descriptor is a connection socket and rst=true,
// the peer connection will be marked as "reset" instead
// of simply closed.
static void desc_free(Desc *desc, lfs_t *lfs, bool rst)
static void desc_free(Desc *desc, bool rst)
{
switch (desc->type) {
case DESC_EMPTY:
@@ -801,10 +807,10 @@ static void desc_free(Desc *desc, lfs_t *lfs, bool rst)
socket_queue_unref(desc->output);
break;
case DESC_FILE:
lfs_file_close(lfs, &desc->file);
mockfs_close_file(&desc->file);
break;
case DESC_DIRECTORY:
lfs_dir_close(lfs, &desc->dir);
mockfs_close_dir(&desc->dir);
break;
default:
UNREACHABLE;
@@ -877,64 +883,6 @@ static int split_args(char *arg, char **argv, int max_argc)
return argc;
}
static int block_device_read(const struct lfs_config *c,
lfs_block_t block, lfs_off_t off, void *buffer, lfs_size_t size)
{
Host *host = c->context;
// Block offset
lfs_off_t abs_off = block * c->block_size + off;
// Bounds check
if (abs_off + size > (lfs_size_t) host->disk_size)
return LFS_ERR_IO;
// Copy data from disk to buffer
memcpy(buffer, host->disk_data + abs_off, size);
return LFS_ERR_OK;
}
static int block_device_prog(const struct lfs_config *c,
lfs_block_t block, lfs_off_t off, const void *buffer, lfs_size_t size)
{
Host *host = c->context;
// Block offset
lfs_off_t abs_off = block * c->block_size + off;
// Bounds check
if (abs_off + size > (lfs_size_t) host->disk_size)
return LFS_ERR_IO;
// Copy data from buffer to disk
memcpy(host->disk_data + abs_off, buffer, size);
return LFS_ERR_OK;
}
static int block_device_erase(const struct lfs_config *c,
lfs_block_t block)
{
Host *host = c->context;
// Block offset
lfs_off_t abs_off = block * c->block_size;
// Bounds check
if (abs_off + c->block_size > (lfs_size_t) host->disk_size)
return LFS_ERR_IO;
// Erase the block by setting all bytes to 0xFF (typical erased flash state)
memset(host->disk_data + abs_off, 0xFF, c->block_size);
return LFS_ERR_OK;
}
static int block_device_sync(const struct lfs_config *c)
{
// No-op for in-memory storage - nothing to flush
(void) c;
return LFS_ERR_OK;
}
static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
{
host->sim = sim;
@@ -989,34 +937,10 @@ static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
// Zero out memory to make sure operations are deterministic
memset(host->disk_data, 0, config.disk_size);
host->lfs_cfg = (struct lfs_config) {
.context = host,
// block device operations
.read = block_device_read,
.prog = block_device_prog,
.erase = block_device_erase,
.sync = block_device_sync,
// block device configuration
.read_size = 16,
.prog_size = 16,
.block_size = 4096,
.block_count = 128,
.cache_size = 16,
.lookahead_size = 16,
.block_cycles = 500,
};
int ret = lfs_mount(&host->lfs, &host->lfs_cfg);
if (ret) {
lfs_format(&host->lfs, &host->lfs_cfg); // TODO: can this fail?
ret = lfs_mount(&host->lfs, &host->lfs_cfg);
if (ret) {
int ret = mockfs_init(&host->mfs, host->disk_data, config.disk_size);
if (ret < 0) {
TODO;
}
}
host->timedout = false;
host->poll_count = 0;
@@ -1053,12 +977,12 @@ static void host_free(Host *host)
TODO;
}
lfs_unmount(&host->lfs);
mockfs_free(host->mfs);
free(host->disk_data);
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
if (host->desc[i].type != DESC_EMPTY)
desc_free(&host->desc[i], &host->lfs, true);
desc_free(&host->desc[i], true);
}
free(host->state);
@@ -1349,7 +1273,7 @@ static int host_close(Host *host, int desc_idx, bool expect_socket)
if (host->desc[desc_idx].type == DESC_SOCKET_C)
time_event_disconnect(host->sim, 10000000, &host->desc[desc_idx], false);
desc_free(&host->desc[desc_idx], &host->lfs, false);
desc_free(&host->desc[desc_idx], false);
host->num_desc--;
return 0;
}
@@ -1603,6 +1527,27 @@ static int host_connect_status(Host *host, int desc_idx, ConnectStatus *status)
return 0;
}
static int mockfs_to_quakey_error(int err)
{
switch (err) {
case 0: return 0;
case MOCKFS_ERRNO_NOENT : return HOST_ERROR_NOENT;
case MOCKFS_ERRNO_PERM : return HOST_ERROR_PERM;
case MOCKFS_ERRNO_NOMEM : return HOST_ERROR_NOMEM;
case MOCKFS_ERRNO_NOTDIR: return HOST_ERROR_NOTDIR;
case MOCKFS_ERRNO_ISDIR : return HOST_ERROR_ISDIR;
case MOCKFS_ERRNO_INVAL : return HOST_ERROR_BADARG;
case MOCKFS_ERRNO_NOTEMPTY: return HOST_ERROR_NOTEMPTY;
case MOCKFS_ERRNO_NOSPC : return HOST_ERROR_NOSPC;
case MOCKFS_ERRNO_EXIST : return HOST_ERROR_EXIST;
case MOCKFS_ERRNO_BUSY : return HOST_ERROR_BUSY;
case MOCKFS_ERRNO_BADF : return HOST_ERROR_BADF;
default:
printf("Unexpected mockfs errno %d\n", err);
assert(0);
}
}
static int host_open_file(Host *host, char *path, int flags)
{
int desc_idx = find_empty_desc_struct(host);
@@ -1610,12 +1555,9 @@ static int host_open_file(Host *host, char *path, int flags)
return HOST_ERROR_FULL;
Desc *desc = &host->desc[desc_idx];
int ret = lfs_file_open(&host->lfs, &desc->file, path, flags);
if (ret < 0) {
if (ret == LFS_ERR_NOENT)
return HOST_ERROR_NOENT;
return HOST_ERROR_IO;
}
int ret = mockfs_open(host->mfs, path, strlen(path), flags, &desc->file);
if (ret < 0)
return mockfs_to_quakey_error(ret);
desc->type = DESC_FILE;
desc->non_blocking = false;
@@ -1631,15 +1573,9 @@ static int host_open_dir(Host *host, char *path)
return HOST_ERROR_FULL;
Desc *desc = &host->desc[desc_idx];
int ret = lfs_dir_open(&host->lfs, &desc->dir, path);
if (ret < 0) {
switch (ret) {
case LFS_ERR_NOENT:
return HOST_ERROR_NOENT;
default:
return HOST_ERROR_IO;
}
}
int ret = mockfs_open_dir(host->mfs, path, strlen(path), &desc->dir);
if (ret < 0)
return mockfs_to_quakey_error(ret);
desc->type = DESC_DIRECTORY;
desc->non_blocking = false;
@@ -1657,23 +1593,22 @@ static int host_read_dir(Host *host, int desc_idx, DirEntry *entry)
if (desc->type != DESC_DIRECTORY)
return HOST_ERROR_BADARG;
struct lfs_info info;
int ret = lfs_dir_read(&host->lfs, &desc->dir, &info);
if (ret < 0)
return HOST_ERROR_IO;
if (ret == 0)
return 0; // End of directory
MockFS_Dirent buf;
int ret = mockfs_read_dir(&desc->dir, &buf);
if (ret < 0) {
if (ret == MOCKFS_ERRNO_NOENT)
return 0;
return mockfs_to_quakey_error(ret);
}
// Copy entry information
// LFS_NAME_MAX is typically 255, and our name buffer is 256
int i = 0;
while (info.name[i] != '\0' && i < 255) {
entry->name[i] = info.name[i];
while (i < buf.name_len && i < 255) {
entry->name[i] = buf.name[i];
i++;
}
entry->name[i] = '\0';
entry->is_dir = (info.type == LFS_TYPE_DIR);
entry->is_dir = buf.is_dir;
return 1;
}
@@ -1729,10 +1664,9 @@ static int host_read(Host *host, int desc_idx, char *dst, int len)
if (desc->type == DESC_SOCKET_C) {
num = recv_inner(desc, dst, len);
} else if (desc->type == DESC_FILE) {
// TODO: what if the file wasn't open for reading?
lfs_ssize_t ret = lfs_file_read(&host->lfs, &desc->file, dst, len);
int ret = mockfs_read(&desc->file, dst, len);
if (ret < 0)
return HOST_ERROR_IO;
return mockfs_to_quakey_error(ret);
num = ret;
} else {
if (desc->type == DESC_DIRECTORY)
@@ -1753,10 +1687,9 @@ static int host_write(Host *host, int desc_idx, char *src, int len)
if (desc->type == DESC_SOCKET_C) {
num = send_inner(desc, src, len);
} else if (desc->type == DESC_FILE) {
// TODO: what if the file wasn't open for writing?
lfs_ssize_t ret = lfs_file_write(&host->lfs, &desc->file, src, len);
int ret = mockfs_write(&desc->file, src, len);
if (ret < 0)
return HOST_ERROR_IO; // TODO: this may be imprecise
return mockfs_to_quakey_error(ret);
num = ret;
} else {
return HOST_ERROR_BADIDX;
@@ -1801,53 +1734,25 @@ static int host_send(Host *host, int desc_idx, char *src, int len)
static int host_mkdir(Host *host, char *path)
{
int ret = lfs_mkdir(&host->lfs, path);
if (ret < 0) {
switch (ret) {
case LFS_ERR_EXIST:
return HOST_ERROR_EXISTS;
case LFS_ERR_NOENT:
return HOST_ERROR_NOENT;
default:
return HOST_ERROR_IO;
}
}
int ret = mockfs_mkdir(host->mfs, path, strlen(path));
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_remove(Host *host, char *path)
{
int ret = lfs_remove(&host->lfs, path);
if (ret < 0) {
switch (ret) {
case LFS_ERR_NOENT:
return HOST_ERROR_NOENT;
case LFS_ERR_NOTEMPTY:
return HOST_ERROR_NOTEMPTY;
default:
return HOST_ERROR_IO;
}
}
int ret = mockfs_remove(host->mfs, path, strlen(path), false);
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
static int host_rename(Host *host, char *oldpath, char *newpath)
{
int ret = lfs_rename(&host->lfs, oldpath, newpath);
if (ret < 0) {
switch (ret) {
case LFS_ERR_NOENT:
return HOST_ERROR_NOENT;
case LFS_ERR_EXIST:
return HOST_ERROR_EXIST;
case LFS_ERR_NOTEMPTY:
return HOST_ERROR_NOTEMPTY;
case LFS_ERR_ISDIR:
return HOST_ERROR_ISDIR;
default:
return HOST_ERROR_IO;
}
}
int ret = mockfs_rename(host->mfs, oldpath, strlen(oldpath), newpath, strlen(newpath));
if (ret < 0)
return mockfs_to_quakey_error(ret);
return 0;
}
@@ -1860,7 +1765,7 @@ static int host_fileinfo(Host *host, int desc_idx, FileInfo *info)
switch (desc->type) {
case DESC_FILE:
{
lfs_soff_t size = lfs_file_size(&host->lfs, &desc->file);
int size = mockfs_file_size(&desc->file);
if (size < 0)
return HOST_ERROR_IO;
info->size = size;
@@ -1892,19 +1797,19 @@ static int host_lseek(Host *host, int desc_idx, int64_t offset, int whence)
int lfs_whence;
switch (whence) {
case HOST_SEEK_SET:
lfs_whence = LFS_SEEK_SET;
lfs_whence = MOCKFS_SEEK_SET;
break;
case HOST_SEEK_CUR:
lfs_whence = LFS_SEEK_CUR;
lfs_whence = MOCKFS_SEEK_CUR;
break;
case HOST_SEEK_END:
lfs_whence = LFS_SEEK_END;
lfs_whence = MOCKFS_SEEK_END;
break;
default:
return HOST_ERROR_BADARG;
}
lfs_soff_t ret = lfs_file_seek(&host->lfs, &desc->file, (lfs_soff_t) offset, lfs_whence);
int ret = mockfs_lseek(&desc->file, offset, lfs_whence);
if (ret < 0)
return HOST_ERROR_BADARG;
@@ -1920,9 +1825,9 @@ static int host_fsync(Host *host, int desc_idx)
if (desc->type != DESC_FILE)
return HOST_ERROR_BADIDX;
int ret = lfs_file_sync(&host->lfs, &desc->file);
int ret = mockfs_sync(&desc->file);
if (ret < 0)
return HOST_ERROR_IO;
return mockfs_to_quakey_error(ret);
return 0;
}
@@ -2571,25 +2476,23 @@ int mock_connect(int fd, void *addr, unsigned long addr_len)
return -1;
}
static int convert_linux_open_flags_to_lfs(int flags)
static int convert_linux_open_flags_to_mockfs(int flags)
{
int lfs_flags = 0;
// Convert access mode (lowest 2 bits)
// Linux: O_RDONLY=0, O_WRONLY=1, O_RDWR=2
// LFS: LFS_O_RDONLY=1, LFS_O_WRONLY=2, LFS_O_RDWR=3
int access_mode = flags & 3;
lfs_flags = access_mode + 1;
// Convert other flags
if (flags & O_RDWR)
lfs_flags |= MOCKFS_O_RDWR;
if (flags & O_WRONLY)
lfs_flags |= MOCKFS_O_WRONLY;
if (flags & O_CREAT)
lfs_flags |= LFS_O_CREAT;
lfs_flags |= MOCKFS_O_CREAT;
if (flags & O_EXCL)
lfs_flags |= LFS_O_EXCL;
lfs_flags |= MOCKFS_O_EXCL;
if (flags & O_TRUNC)
lfs_flags |= LFS_O_TRUNC;
lfs_flags |= MOCKFS_O_TRUNC;
if (flags & O_APPEND)
lfs_flags |= LFS_O_APPEND;
lfs_flags |= MOCKFS_O_APPEND;
return lfs_flags;
}
@@ -2603,7 +2506,7 @@ int mock_open(char *path, int flags, int mode)
if (!host_is_linux(host))
abort_("Call to mock_open() not from Linux\n");
int converted_flags = convert_linux_open_flags_to_lfs(flags);
int converted_flags = convert_linux_open_flags_to_mockfs(flags);
int ret = host_open_file(host, path, converted_flags);
if (ret < 0) {
@@ -3153,7 +3056,7 @@ char *mock_realpath(char *path, char *dst)
// Unlike _fullpath, realpath requires the path to exist
// Try to open as file first, then as directory
int fd = host_open_file(host, result, LFS_O_RDONLY);
int fd = host_open_file(host, result, MOCKFS_O_RDONLY);
if (fd >= 0) {
host_close(host, fd, false);
} else {
+3 -3
View File
@@ -137,7 +137,7 @@ int main(void)
.free_func = random_client_free,
.addrs = (char*[]) { "127.0.0.2" },
.num_addrs = 1,
.disk_size = 1<<20,
.disk_size = 10<<20,
.platform = QUAKEY_LINUX,
};
quakey_spawn(quakey, config, "cli --server 127.0.0.3 8080");
@@ -152,7 +152,7 @@ int main(void)
.free_func = metadata_server_free,
.addrs = (char*[]) { "127.0.0.3" },
.num_addrs = 1,
.disk_size = 1<<20,
.disk_size = 10<<20,
.platform = QUAKEY_LINUX,
};
quakey_spawn(quakey, config, "ms --addr 127.0.0.3 --port 8080");
@@ -167,7 +167,7 @@ int main(void)
.free_func = chunk_server_free,
.addrs = (char*[]) { "127.0.0.4" },
.num_addrs = 1,
.disk_size = 1<<20,
.disk_size = 10<<20,
.platform = QUAKEY_LINUX,
};
quakey_spawn(quakey, config, "cs --addr 127.0.0.4 --port 8081 --remote-addr 127.0.0.3 --remote-port 8080");