Replace LittleFS with MockFS
This commit is contained in:
@@ -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_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
|
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
File diff suppressed because it is too large
Load Diff
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
+89
-186
@@ -1,8 +1,10 @@
|
|||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Includes
|
// Includes
|
||||||
|
|
||||||
#include "lfs.h"
|
#include "mockfs.h"
|
||||||
#include <quakey.h>
|
#include <quakey.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////
|
||||||
// Utilities
|
// Utilities
|
||||||
@@ -170,12 +172,12 @@ struct Desc {
|
|||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// File fields
|
// File fields
|
||||||
|
|
||||||
lfs_file_t file;
|
MockFS_OpenFile file;
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
// Directory fields
|
// Directory fields
|
||||||
|
|
||||||
lfs_dir_t dir;
|
MockFS_OpenDir dir;
|
||||||
|
|
||||||
/////////////////////////////////////////
|
/////////////////////////////////////////
|
||||||
};
|
};
|
||||||
@@ -210,6 +212,11 @@ enum {
|
|||||||
HOST_ERROR_NOTEMPTY = -18,
|
HOST_ERROR_NOTEMPTY = -18,
|
||||||
HOST_ERROR_EXIST = -19,
|
HOST_ERROR_EXIST = -19,
|
||||||
HOST_ERROR_EXISTS = -19, // Alias for HOST_ERROR_EXIST
|
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
|
// lseek whence values for host_lseek
|
||||||
@@ -276,9 +283,8 @@ struct Host {
|
|||||||
int disk_size;
|
int disk_size;
|
||||||
char *disk_data;
|
char *disk_data;
|
||||||
|
|
||||||
// LittleFS instance managing the disk bytes
|
// MockFS instance managing the disk bytes
|
||||||
lfs_t lfs;
|
MockFS *mfs;
|
||||||
struct lfs_config lfs_cfg;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -767,7 +773,7 @@ static bool accept_queue_empty(AcceptQueue *queue)
|
|||||||
// If the descriptor is a connection socket and rst=true,
|
// If the descriptor is a connection socket and rst=true,
|
||||||
// the peer connection will be marked as "reset" instead
|
// the peer connection will be marked as "reset" instead
|
||||||
// of simply closed.
|
// 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) {
|
switch (desc->type) {
|
||||||
case DESC_EMPTY:
|
case DESC_EMPTY:
|
||||||
@@ -801,10 +807,10 @@ static void desc_free(Desc *desc, lfs_t *lfs, bool rst)
|
|||||||
socket_queue_unref(desc->output);
|
socket_queue_unref(desc->output);
|
||||||
break;
|
break;
|
||||||
case DESC_FILE:
|
case DESC_FILE:
|
||||||
lfs_file_close(lfs, &desc->file);
|
mockfs_close_file(&desc->file);
|
||||||
break;
|
break;
|
||||||
case DESC_DIRECTORY:
|
case DESC_DIRECTORY:
|
||||||
lfs_dir_close(lfs, &desc->dir);
|
mockfs_close_dir(&desc->dir);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
UNREACHABLE;
|
UNREACHABLE;
|
||||||
@@ -877,64 +883,6 @@ static int split_args(char *arg, char **argv, int max_argc)
|
|||||||
return 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)
|
static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
|
||||||
{
|
{
|
||||||
host->sim = sim;
|
host->sim = sim;
|
||||||
@@ -989,33 +937,9 @@ static void host_init(Host *host, Sim *sim, QuakeySpawn config, char *arg)
|
|||||||
// Zero out memory to make sure operations are deterministic
|
// Zero out memory to make sure operations are deterministic
|
||||||
memset(host->disk_data, 0, config.disk_size);
|
memset(host->disk_data, 0, config.disk_size);
|
||||||
|
|
||||||
host->lfs_cfg = (struct lfs_config) {
|
int ret = mockfs_init(&host->mfs, host->disk_data, config.disk_size);
|
||||||
|
if (ret < 0) {
|
||||||
.context = host,
|
TODO;
|
||||||
|
|
||||||
// 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) {
|
|
||||||
TODO;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
host->timedout = false;
|
host->timedout = false;
|
||||||
@@ -1053,12 +977,12 @@ static void host_free(Host *host)
|
|||||||
TODO;
|
TODO;
|
||||||
}
|
}
|
||||||
|
|
||||||
lfs_unmount(&host->lfs);
|
mockfs_free(host->mfs);
|
||||||
free(host->disk_data);
|
free(host->disk_data);
|
||||||
|
|
||||||
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
|
for (int i = 0; i < HOST_DESC_LIMIT; i++) {
|
||||||
if (host->desc[i].type != DESC_EMPTY)
|
if (host->desc[i].type != DESC_EMPTY)
|
||||||
desc_free(&host->desc[i], &host->lfs, true);
|
desc_free(&host->desc[i], true);
|
||||||
}
|
}
|
||||||
|
|
||||||
free(host->state);
|
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)
|
if (host->desc[desc_idx].type == DESC_SOCKET_C)
|
||||||
time_event_disconnect(host->sim, 10000000, &host->desc[desc_idx], false);
|
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--;
|
host->num_desc--;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1603,6 +1527,27 @@ static int host_connect_status(Host *host, int desc_idx, ConnectStatus *status)
|
|||||||
return 0;
|
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)
|
static int host_open_file(Host *host, char *path, int flags)
|
||||||
{
|
{
|
||||||
int desc_idx = find_empty_desc_struct(host);
|
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;
|
return HOST_ERROR_FULL;
|
||||||
Desc *desc = &host->desc[desc_idx];
|
Desc *desc = &host->desc[desc_idx];
|
||||||
|
|
||||||
int ret = lfs_file_open(&host->lfs, &desc->file, path, flags);
|
int ret = mockfs_open(host->mfs, path, strlen(path), flags, &desc->file);
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
if (ret == LFS_ERR_NOENT)
|
return mockfs_to_quakey_error(ret);
|
||||||
return HOST_ERROR_NOENT;
|
|
||||||
return HOST_ERROR_IO;
|
|
||||||
}
|
|
||||||
|
|
||||||
desc->type = DESC_FILE;
|
desc->type = DESC_FILE;
|
||||||
desc->non_blocking = false;
|
desc->non_blocking = false;
|
||||||
@@ -1631,15 +1573,9 @@ static int host_open_dir(Host *host, char *path)
|
|||||||
return HOST_ERROR_FULL;
|
return HOST_ERROR_FULL;
|
||||||
Desc *desc = &host->desc[desc_idx];
|
Desc *desc = &host->desc[desc_idx];
|
||||||
|
|
||||||
int ret = lfs_dir_open(&host->lfs, &desc->dir, path);
|
int ret = mockfs_open_dir(host->mfs, path, strlen(path), &desc->dir);
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
switch (ret) {
|
return mockfs_to_quakey_error(ret);
|
||||||
case LFS_ERR_NOENT:
|
|
||||||
return HOST_ERROR_NOENT;
|
|
||||||
default:
|
|
||||||
return HOST_ERROR_IO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
desc->type = DESC_DIRECTORY;
|
desc->type = DESC_DIRECTORY;
|
||||||
desc->non_blocking = false;
|
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)
|
if (desc->type != DESC_DIRECTORY)
|
||||||
return HOST_ERROR_BADARG;
|
return HOST_ERROR_BADARG;
|
||||||
|
|
||||||
struct lfs_info info;
|
MockFS_Dirent buf;
|
||||||
int ret = lfs_dir_read(&host->lfs, &desc->dir, &info);
|
int ret = mockfs_read_dir(&desc->dir, &buf);
|
||||||
if (ret < 0)
|
if (ret < 0) {
|
||||||
return HOST_ERROR_IO;
|
if (ret == MOCKFS_ERRNO_NOENT)
|
||||||
|
return 0;
|
||||||
if (ret == 0)
|
return mockfs_to_quakey_error(ret);
|
||||||
return 0; // End of directory
|
}
|
||||||
|
|
||||||
// Copy entry information
|
// Copy entry information
|
||||||
// LFS_NAME_MAX is typically 255, and our name buffer is 256
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (info.name[i] != '\0' && i < 255) {
|
while (i < buf.name_len && i < 255) {
|
||||||
entry->name[i] = info.name[i];
|
entry->name[i] = buf.name[i];
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
entry->name[i] = '\0';
|
entry->name[i] = '\0';
|
||||||
entry->is_dir = (info.type == LFS_TYPE_DIR);
|
entry->is_dir = buf.is_dir;
|
||||||
|
|
||||||
return 1;
|
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) {
|
if (desc->type == DESC_SOCKET_C) {
|
||||||
num = recv_inner(desc, dst, len);
|
num = recv_inner(desc, dst, len);
|
||||||
} else if (desc->type == DESC_FILE) {
|
} else if (desc->type == DESC_FILE) {
|
||||||
// TODO: what if the file wasn't open for reading?
|
int ret = mockfs_read(&desc->file, dst, len);
|
||||||
lfs_ssize_t ret = lfs_file_read(&host->lfs, &desc->file, dst, len);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return HOST_ERROR_IO;
|
return mockfs_to_quakey_error(ret);
|
||||||
num = ret;
|
num = ret;
|
||||||
} else {
|
} else {
|
||||||
if (desc->type == DESC_DIRECTORY)
|
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) {
|
if (desc->type == DESC_SOCKET_C) {
|
||||||
num = send_inner(desc, src, len);
|
num = send_inner(desc, src, len);
|
||||||
} else if (desc->type == DESC_FILE) {
|
} else if (desc->type == DESC_FILE) {
|
||||||
// TODO: what if the file wasn't open for writing?
|
int ret = mockfs_write(&desc->file, src, len);
|
||||||
lfs_ssize_t ret = lfs_file_write(&host->lfs, &desc->file, src, len);
|
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return HOST_ERROR_IO; // TODO: this may be imprecise
|
return mockfs_to_quakey_error(ret);
|
||||||
num = ret;
|
num = ret;
|
||||||
} else {
|
} else {
|
||||||
return HOST_ERROR_BADIDX;
|
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)
|
static int host_mkdir(Host *host, char *path)
|
||||||
{
|
{
|
||||||
int ret = lfs_mkdir(&host->lfs, path);
|
int ret = mockfs_mkdir(host->mfs, path, strlen(path));
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
switch (ret) {
|
return mockfs_to_quakey_error(ret);
|
||||||
case LFS_ERR_EXIST:
|
|
||||||
return HOST_ERROR_EXISTS;
|
|
||||||
case LFS_ERR_NOENT:
|
|
||||||
return HOST_ERROR_NOENT;
|
|
||||||
default:
|
|
||||||
return HOST_ERROR_IO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int host_remove(Host *host, char *path)
|
static int host_remove(Host *host, char *path)
|
||||||
{
|
{
|
||||||
int ret = lfs_remove(&host->lfs, path);
|
int ret = mockfs_remove(host->mfs, path, strlen(path), false);
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
switch (ret) {
|
return mockfs_to_quakey_error(ret);
|
||||||
case LFS_ERR_NOENT:
|
|
||||||
return HOST_ERROR_NOENT;
|
|
||||||
case LFS_ERR_NOTEMPTY:
|
|
||||||
return HOST_ERROR_NOTEMPTY;
|
|
||||||
default:
|
|
||||||
return HOST_ERROR_IO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int host_rename(Host *host, char *oldpath, char *newpath)
|
static int host_rename(Host *host, char *oldpath, char *newpath)
|
||||||
{
|
{
|
||||||
int ret = lfs_rename(&host->lfs, oldpath, newpath);
|
int ret = mockfs_rename(host->mfs, oldpath, strlen(oldpath), newpath, strlen(newpath));
|
||||||
if (ret < 0) {
|
if (ret < 0)
|
||||||
switch (ret) {
|
return mockfs_to_quakey_error(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1860,7 +1765,7 @@ static int host_fileinfo(Host *host, int desc_idx, FileInfo *info)
|
|||||||
switch (desc->type) {
|
switch (desc->type) {
|
||||||
case DESC_FILE:
|
case DESC_FILE:
|
||||||
{
|
{
|
||||||
lfs_soff_t size = lfs_file_size(&host->lfs, &desc->file);
|
int size = mockfs_file_size(&desc->file);
|
||||||
if (size < 0)
|
if (size < 0)
|
||||||
return HOST_ERROR_IO;
|
return HOST_ERROR_IO;
|
||||||
info->size = size;
|
info->size = size;
|
||||||
@@ -1892,19 +1797,19 @@ static int host_lseek(Host *host, int desc_idx, int64_t offset, int whence)
|
|||||||
int lfs_whence;
|
int lfs_whence;
|
||||||
switch (whence) {
|
switch (whence) {
|
||||||
case HOST_SEEK_SET:
|
case HOST_SEEK_SET:
|
||||||
lfs_whence = LFS_SEEK_SET;
|
lfs_whence = MOCKFS_SEEK_SET;
|
||||||
break;
|
break;
|
||||||
case HOST_SEEK_CUR:
|
case HOST_SEEK_CUR:
|
||||||
lfs_whence = LFS_SEEK_CUR;
|
lfs_whence = MOCKFS_SEEK_CUR;
|
||||||
break;
|
break;
|
||||||
case HOST_SEEK_END:
|
case HOST_SEEK_END:
|
||||||
lfs_whence = LFS_SEEK_END;
|
lfs_whence = MOCKFS_SEEK_END;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return HOST_ERROR_BADARG;
|
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)
|
if (ret < 0)
|
||||||
return HOST_ERROR_BADARG;
|
return HOST_ERROR_BADARG;
|
||||||
|
|
||||||
@@ -1920,9 +1825,9 @@ static int host_fsync(Host *host, int desc_idx)
|
|||||||
if (desc->type != DESC_FILE)
|
if (desc->type != DESC_FILE)
|
||||||
return HOST_ERROR_BADIDX;
|
return HOST_ERROR_BADIDX;
|
||||||
|
|
||||||
int ret = lfs_file_sync(&host->lfs, &desc->file);
|
int ret = mockfs_sync(&desc->file);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
return HOST_ERROR_IO;
|
return mockfs_to_quakey_error(ret);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -2571,25 +2476,23 @@ int mock_connect(int fd, void *addr, unsigned long addr_len)
|
|||||||
return -1;
|
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;
|
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
|
// 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)
|
if (flags & O_CREAT)
|
||||||
lfs_flags |= LFS_O_CREAT;
|
lfs_flags |= MOCKFS_O_CREAT;
|
||||||
if (flags & O_EXCL)
|
if (flags & O_EXCL)
|
||||||
lfs_flags |= LFS_O_EXCL;
|
lfs_flags |= MOCKFS_O_EXCL;
|
||||||
if (flags & O_TRUNC)
|
if (flags & O_TRUNC)
|
||||||
lfs_flags |= LFS_O_TRUNC;
|
lfs_flags |= MOCKFS_O_TRUNC;
|
||||||
if (flags & O_APPEND)
|
if (flags & O_APPEND)
|
||||||
lfs_flags |= LFS_O_APPEND;
|
lfs_flags |= MOCKFS_O_APPEND;
|
||||||
|
|
||||||
return lfs_flags;
|
return lfs_flags;
|
||||||
}
|
}
|
||||||
@@ -2603,7 +2506,7 @@ int mock_open(char *path, int flags, int mode)
|
|||||||
if (!host_is_linux(host))
|
if (!host_is_linux(host))
|
||||||
abort_("Call to mock_open() not from Linux\n");
|
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);
|
int ret = host_open_file(host, path, converted_flags);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -3153,7 +3056,7 @@ char *mock_realpath(char *path, char *dst)
|
|||||||
|
|
||||||
// Unlike _fullpath, realpath requires the path to exist
|
// Unlike _fullpath, realpath requires the path to exist
|
||||||
// Try to open as file first, then as directory
|
// 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) {
|
if (fd >= 0) {
|
||||||
host_close(host, fd, false);
|
host_close(host, fd, false);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+3
-3
@@ -137,7 +137,7 @@ int main(void)
|
|||||||
.free_func = random_client_free,
|
.free_func = random_client_free,
|
||||||
.addrs = (char*[]) { "127.0.0.2" },
|
.addrs = (char*[]) { "127.0.0.2" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 1<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
.platform = QUAKEY_LINUX,
|
||||||
};
|
};
|
||||||
quakey_spawn(quakey, config, "cli --server 127.0.0.3 8080");
|
quakey_spawn(quakey, config, "cli --server 127.0.0.3 8080");
|
||||||
@@ -152,7 +152,7 @@ int main(void)
|
|||||||
.free_func = metadata_server_free,
|
.free_func = metadata_server_free,
|
||||||
.addrs = (char*[]) { "127.0.0.3" },
|
.addrs = (char*[]) { "127.0.0.3" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 1<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
.platform = QUAKEY_LINUX,
|
||||||
};
|
};
|
||||||
quakey_spawn(quakey, config, "ms --addr 127.0.0.3 --port 8080");
|
quakey_spawn(quakey, config, "ms --addr 127.0.0.3 --port 8080");
|
||||||
@@ -167,7 +167,7 @@ int main(void)
|
|||||||
.free_func = chunk_server_free,
|
.free_func = chunk_server_free,
|
||||||
.addrs = (char*[]) { "127.0.0.4" },
|
.addrs = (char*[]) { "127.0.0.4" },
|
||||||
.num_addrs = 1,
|
.num_addrs = 1,
|
||||||
.disk_size = 1<<20,
|
.disk_size = 10<<20,
|
||||||
.platform = QUAKEY_LINUX,
|
.platform = QUAKEY_LINUX,
|
||||||
};
|
};
|
||||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.4 --port 8081 --remote-addr 127.0.0.3 --remote-port 8080");
|
quakey_spawn(quakey, config, "cs --addr 127.0.0.4 --port 8081 --remote-addr 127.0.0.3 --remote-port 8080");
|
||||||
|
|||||||
Reference in New Issue
Block a user