1155 lines
30 KiB
C
1155 lines
30 KiB
C
/*
|
|
* Copyright (c) 2015 Gurjant Kalsi <me@gurjantkalsi.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files
|
|
* (the "Software"), to deal in the Software without restriction,
|
|
* including without limitation the rights to use, copy, modify, merge,
|
|
* publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <debug.h>
|
|
#include <err.h>
|
|
#include <pow2.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <trace.h>
|
|
|
|
#include <kernel/mutex.h>
|
|
#include <lib/bio.h>
|
|
#include <lib/cksum.h>
|
|
#include <lib/console.h>
|
|
#include <lib/fs.h>
|
|
#include <lib/fs/spifs.h>
|
|
#include <list.h>
|
|
#include <lk/init.h>
|
|
|
|
#define LOCAL_TRACE 0
|
|
|
|
#define FS_VERSION 1
|
|
#define FS_MAGIC 0x53504653 // SPFS
|
|
|
|
#define SPIFS_ENTRY_LENGTH 32
|
|
|
|
#define TOC_HEADER_RESERVED_BYTES 16
|
|
#define TOC_FOOTER_RESERVED_BYTES 28
|
|
#define MAX_FILENAME_LENGTH 20
|
|
|
|
#define CORRUPT_TOC 0
|
|
#define NO_OPEN_RUNS 0
|
|
|
|
#define FRONT_TOC (1)
|
|
#define BACK_TOC (-1)
|
|
|
|
#define FRONT_TOC_LABEL "front-toc"
|
|
#define BACK_TOC_LABEL "back-toc"
|
|
|
|
typedef int32_t toc_position_t;
|
|
|
|
typedef struct {
|
|
uint8_t* page;
|
|
uint32_t page_size;
|
|
uint32_t page_count;
|
|
uint32_t blocks_per_page;
|
|
|
|
uint32_t generation;
|
|
uint32_t num_entries;
|
|
toc_position_t toc_position;
|
|
|
|
struct list_node files;
|
|
struct list_node dcookies;
|
|
|
|
bdev_t *dev;
|
|
|
|
mutex_t lock;
|
|
} spifs_t;
|
|
|
|
typedef struct {
|
|
uint32_t magic;
|
|
uint32_t version;
|
|
uint32_t num_entries;
|
|
uint32_t generation;
|
|
|
|
uint8_t _reserved[TOC_HEADER_RESERVED_BYTES];
|
|
} toc_header_t;
|
|
|
|
typedef struct {
|
|
uint32_t page_idx;
|
|
uint32_t length;
|
|
uint32_t capacity;
|
|
char filename[MAX_FILENAME_LENGTH];
|
|
} toc_file_t;
|
|
|
|
typedef struct {
|
|
uint8_t _reserved[TOC_FOOTER_RESERVED_BYTES];
|
|
uint32_t checksum;
|
|
} toc_footer_t;
|
|
|
|
typedef struct {
|
|
struct list_node node;
|
|
spifs_t *fs_handle;
|
|
toc_file_t metadata;
|
|
} spifs_file_t;
|
|
|
|
struct dircookie {
|
|
struct list_node node;
|
|
spifs_t *fs;
|
|
|
|
spifs_file_t *next_file;
|
|
};
|
|
|
|
typedef struct {
|
|
uint32_t page_id;
|
|
int32_t direction;
|
|
uint32_t entry_length;
|
|
uint8_t *data;
|
|
uint8_t *page;
|
|
spifs_t *spifs;
|
|
} cursor_t;
|
|
|
|
static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr);
|
|
static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr);
|
|
|
|
static status_t get_device_page_info(bdev_t* dev, uint32_t* page_size,
|
|
uint32_t* page_count);
|
|
|
|
|
|
static status_t cursor_init(
|
|
cursor_t *cursor, spifs_t *spifs, int32_t direction, uint32_t page_id,
|
|
uint32_t entry_length
|
|
)
|
|
{
|
|
// Make sure the cursor can only be advanced an integer number of times
|
|
// per page.
|
|
DEBUG_ASSERT(ispow2(entry_length));
|
|
DEBUG_ASSERT(spifs->page_size % entry_length == 0);
|
|
DEBUG_ASSERT(spifs->page);
|
|
|
|
cursor->page_id = page_id;
|
|
cursor->direction = direction;
|
|
cursor->entry_length = entry_length;
|
|
cursor->data = spifs->page;
|
|
cursor->spifs = spifs;
|
|
|
|
|
|
return spifs_read_page(spifs, page_id);
|
|
}
|
|
|
|
static uint8_t *cursor_get(cursor_t* cursor)
|
|
{
|
|
spifs_t *spifs = cursor->spifs;
|
|
|
|
uint8_t *page_end = spifs->page + spifs->page_size;
|
|
DEBUG_ASSERT(cursor->data < page_end);
|
|
|
|
return cursor->data;
|
|
}
|
|
|
|
static status_t cursor_advance(cursor_t *cursor)
|
|
{
|
|
spifs_t *spifs = cursor->spifs;
|
|
|
|
uint8_t *page_end = spifs->page + spifs->page_size;
|
|
|
|
cursor->data += cursor->entry_length;
|
|
|
|
// We have walked past the end of our buffer.
|
|
DEBUG_ASSERT(page_end >= cursor->data);
|
|
|
|
// If we're at the end of this page, read the next page and move the cursor
|
|
// to the beginning of it.
|
|
if (cursor->data == page_end) {
|
|
cursor->page_id += cursor->direction;
|
|
cursor->data = spifs->page;
|
|
|
|
return spifs_read_page(spifs, cursor->page_id);
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static spifs_file_t *find_file(spifs_t *spifs, const char* name)
|
|
{
|
|
spifs_file_t *file;
|
|
|
|
list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
|
|
// Skip the ToC Entries
|
|
if (file == list_peek_head_type(&spifs->files, spifs_file_t, node) ||
|
|
file == list_peek_tail_type(&spifs->files, spifs_file_t, node)) {
|
|
continue;
|
|
}
|
|
|
|
if (!strncmp(name, file->metadata.filename, MAX_FILENAME_LENGTH)) {
|
|
return file;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static uint32_t find_open_run(spifs_t *spifs, uint32_t requested_length)
|
|
{
|
|
spifs_file_t *file;
|
|
list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
|
|
// Number of pages that this file occupies
|
|
uint32_t page_size_shift = log2_uint(file->fs_handle->page_size);
|
|
|
|
uint32_t file_page_length =
|
|
divpow2(file->metadata.capacity, page_size_shift);
|
|
|
|
// Index of the page immediately following the last page of this file.
|
|
uint32_t file_end_page = file->metadata.page_idx + file_page_length;
|
|
|
|
// Determine the page that the next file starts at.
|
|
spifs_file_t* next =
|
|
list_next_type(&spifs->files, &file->node, spifs_file_t, node);
|
|
|
|
// End of list?
|
|
if (next == NULL) {
|
|
return NO_OPEN_RUNS;
|
|
}
|
|
|
|
uint32_t available_pages = next->metadata.page_idx - file_end_page;
|
|
uint32_t available_bytes = available_pages * file->fs_handle->page_size;
|
|
if (available_bytes >= requested_length) {
|
|
return file_end_page;
|
|
}
|
|
}
|
|
return NO_OPEN_RUNS;
|
|
}
|
|
|
|
static uint64_t used_space(spifs_t *spifs)
|
|
{
|
|
uint64_t result = 0;
|
|
|
|
spifs_file_t *file;
|
|
list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
|
|
result += file->metadata.capacity;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool consistency_check(spifs_t *spifs)
|
|
{
|
|
/* Return true iff the ToC is in a consistent state. */
|
|
spifs_file_t *file;
|
|
list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
|
|
// Number of pages that this file occupies
|
|
uint32_t file_page_length =
|
|
file->metadata.capacity / file->fs_handle->page_size;
|
|
|
|
// Index of the last page of this file.
|
|
uint32_t file_end_page = file->metadata.page_idx + file_page_length - 1;
|
|
|
|
// Determine the page that the next file starts at.
|
|
spifs_file_t* next =
|
|
list_next_type(&spifs->files, &file->node, spifs_file_t, node);
|
|
|
|
// End of list?
|
|
if (next == NULL) {
|
|
continue;
|
|
}
|
|
|
|
if (next->metadata.page_idx <= file_end_page) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static status_t spifs_commit_toc(spifs_t *spifs)
|
|
{
|
|
status_t err;
|
|
|
|
// Get the next logical ToC.
|
|
toc_position_t target_toc =
|
|
spifs->toc_position == FRONT_TOC ? BACK_TOC : FRONT_TOC;
|
|
|
|
// Bump the generation counter.
|
|
uint32_t target_generation = spifs->generation + 1;
|
|
|
|
uint32_t crc = 0;
|
|
uint8_t *cursor = spifs->page;
|
|
uint32_t toc_page_addr = target_toc == FRONT_TOC ?
|
|
0 : spifs->page_count - 1;
|
|
|
|
// Setup the ToC Header.
|
|
toc_header_t header = {
|
|
.magic = FS_MAGIC,
|
|
.version = FS_VERSION,
|
|
.num_entries = spifs->num_entries,
|
|
.generation = target_generation,
|
|
};
|
|
memset(header._reserved, 0, TOC_HEADER_RESERVED_BYTES);
|
|
|
|
crc = crc32(crc, (uint8_t*)&header, SPIFS_ENTRY_LENGTH);
|
|
|
|
memcpy(cursor, (uint8_t*)&header, SPIFS_ENTRY_LENGTH);
|
|
cursor += SPIFS_ENTRY_LENGTH;
|
|
|
|
// Create an empty file to copy into the empty spots in the ToC
|
|
toc_file_t empty;
|
|
memset(&empty, 0, SPIFS_ENTRY_LENGTH);
|
|
|
|
spifs_file_t *file = list_peek_head_type(&spifs->files, spifs_file_t, node);
|
|
for (uint32_t i = 0; i < spifs->num_entries; i++) {
|
|
uint8_t *page_end = spifs->page + spifs->page_size;
|
|
DEBUG_ASSERT(cursor <= page_end);
|
|
|
|
if (cursor == page_end) {
|
|
err = spifs_write_page(spifs, toc_page_addr);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
toc_page_addr += target_toc;
|
|
cursor = spifs->page;
|
|
}
|
|
|
|
if (file) {
|
|
crc = crc32(crc, (uint8_t*)&file->metadata, SPIFS_ENTRY_LENGTH);
|
|
memcpy(cursor, (uint8_t*)&file->metadata, SPIFS_ENTRY_LENGTH);
|
|
file = list_next_type(&spifs->files, &file->node, spifs_file_t, node);
|
|
} else {
|
|
crc = crc32(crc, (uint8_t*)&empty, SPIFS_ENTRY_LENGTH);
|
|
memcpy(cursor, (uint8_t*)&empty, SPIFS_ENTRY_LENGTH);
|
|
}
|
|
|
|
cursor += SPIFS_ENTRY_LENGTH;
|
|
}
|
|
|
|
// Sanity check. The cursor should be at the last position in this page
|
|
// at this point.
|
|
uint8_t* expected_cursor_location =
|
|
(spifs->page + spifs->page_size) - SPIFS_ENTRY_LENGTH;
|
|
DEBUG_ASSERT(cursor == expected_cursor_location);
|
|
|
|
toc_footer_t footer;
|
|
memset(&footer, 0, SPIFS_ENTRY_LENGTH);
|
|
footer.checksum = crc;
|
|
memcpy(cursor, (uint8_t*)&footer, SPIFS_ENTRY_LENGTH);
|
|
|
|
err = spifs_write_page(spifs, toc_page_addr);
|
|
if (err != NO_ERROR)
|
|
return err;
|
|
|
|
// Only update this once we're sure that the write went through.
|
|
// This way, if the write failed, we'll try writing over the bad ToC again
|
|
// rather than potentially corrupting both ToCs.
|
|
spifs->generation = target_generation;
|
|
spifs->toc_position = target_toc;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static void spifs_add_ascending(spifs_t *spifs, spifs_file_t *target)
|
|
{
|
|
spifs_file_t *file;
|
|
list_for_every_entry(&spifs->files, file, spifs_file_t, node) {
|
|
if (file->metadata.page_idx > target->metadata.page_idx) {
|
|
list_add_before(&file->node, &target->node);
|
|
return;
|
|
}
|
|
}
|
|
|
|
list_add_tail(&spifs->files, &target->node);
|
|
}
|
|
|
|
|
|
static status_t spifs_read_page(spifs_t *spifs, uint32_t page_addr)
|
|
{
|
|
off_t block_addr = page_addr * spifs->blocks_per_page;
|
|
|
|
ssize_t bytes = bio_read_block(spifs->dev, spifs->page, block_addr,
|
|
spifs->blocks_per_page);
|
|
|
|
if ((uint32_t)bytes != spifs->page_size) {
|
|
return ERR_IO;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_write_page(spifs_t *spifs, uint32_t page_addr)
|
|
{
|
|
off_t block_addr = page_addr * spifs->blocks_per_page;
|
|
off_t device_addr = block_addr * spifs->dev->block_size;
|
|
|
|
// Device requires erase before write?
|
|
if (spifs->dev->geometry_count != 0) {
|
|
ssize_t bytes = bio_erase(spifs->dev, device_addr, spifs->page_size);
|
|
if ((uint32_t)bytes != spifs->page_size) {
|
|
return ERR_IO;
|
|
}
|
|
}
|
|
|
|
ssize_t bytes = bio_write_block(spifs->dev, spifs->page, block_addr,
|
|
spifs->blocks_per_page);
|
|
|
|
if ((uint32_t)bytes != spifs->page_size) {
|
|
return ERR_IO;
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static uint32_t get_toc_generation(spifs_t *spifs, toc_position_t toc_pos)
|
|
{
|
|
LTRACEF("spifs %p\n", spifs);
|
|
|
|
uint32_t candidate_generation;
|
|
|
|
DEBUG_ASSERT(spifs);
|
|
|
|
DEBUG_ASSERT(toc_pos == FRONT_TOC || toc_pos == BACK_TOC);
|
|
uint32_t toc_page = toc_pos == FRONT_TOC ?
|
|
0 : (spifs->page_count - 1);
|
|
|
|
|
|
cursor_t cursor;
|
|
if (cursor_init(&cursor, spifs, toc_pos, toc_page, SPIFS_ENTRY_LENGTH) !=
|
|
NO_ERROR) {
|
|
return CORRUPT_TOC;
|
|
}
|
|
|
|
toc_header_t* header = (toc_header_t*)cursor_get(&cursor);
|
|
|
|
if (header->magic != FS_MAGIC) {
|
|
return CORRUPT_TOC;
|
|
}
|
|
|
|
if (header->version != FS_VERSION) {
|
|
return CORRUPT_TOC;
|
|
}
|
|
|
|
candidate_generation = header->generation;
|
|
uint32_t num_toc_entries = header->num_entries;
|
|
|
|
uint32_t crc = 0;
|
|
crc = crc32(crc, (uint8_t*)header, SPIFS_ENTRY_LENGTH);
|
|
|
|
header = NULL;
|
|
|
|
for (size_t i = 0; i < num_toc_entries; i++) {
|
|
if (cursor_advance(&cursor) != NO_ERROR)
|
|
return CORRUPT_TOC;
|
|
|
|
crc = crc32(crc, cursor_get(&cursor), SPIFS_ENTRY_LENGTH);
|
|
}
|
|
|
|
if (cursor_advance(&cursor) != NO_ERROR)
|
|
return CORRUPT_TOC;
|
|
|
|
toc_footer_t *footer = (toc_footer_t *)cursor_get(&cursor);
|
|
if (footer->checksum != crc) {
|
|
return CORRUPT_TOC;
|
|
}
|
|
|
|
return candidate_generation;
|
|
}
|
|
|
|
// page_size will be populated with the device's page size if this function
|
|
// returns NO_ERROR, otherwise the contents of page_size are undefined.
|
|
static status_t get_device_page_info(bdev_t* dev, uint32_t* page_size, uint32_t *page_count)
|
|
{
|
|
LTRACEF("dev %p, page_size %p\n", dev, page_size);
|
|
|
|
switch (dev->geometry_count) {
|
|
case 0: {
|
|
// Device has no erase geometry; overwriting is supported.
|
|
*page_size = dev->block_size;
|
|
*page_count = dev->total_size / (*page_size);
|
|
return NO_ERROR;
|
|
}
|
|
case 1: {
|
|
// Device has erase geometry.
|
|
size_t erase_size = valpow2(dev->geometry->erase_size);
|
|
size_t block_size = dev->block_size;
|
|
|
|
if (erase_size % block_size != 0) {
|
|
// erase_size must be a multiple of the block size.
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
*page_size = erase_size;
|
|
*page_count = dev->total_size / (*page_size);
|
|
return NO_ERROR;
|
|
}
|
|
default: {
|
|
// We don't support non-uniform erase geometry.
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
}
|
|
}
|
|
|
|
static status_t spifs_format(bdev_t* dev, const void* args)
|
|
{
|
|
status_t err = NO_ERROR;
|
|
|
|
LTRACEF("dev %p, args %p\n", dev, args);
|
|
|
|
if (!dev) {
|
|
return ERR_INVALID_ARGS;
|
|
}
|
|
|
|
spifs_format_args_t *spifs_args;
|
|
spifs_format_args_t default_args = {
|
|
.toc_pages = 1,
|
|
};
|
|
|
|
if (!args) {
|
|
spifs_args = &default_args;
|
|
} else {
|
|
spifs_args = (spifs_format_args_t*)args;
|
|
}
|
|
|
|
// Make sure that each of the three data structures are the same size.
|
|
STATIC_ASSERT(sizeof(toc_header_t) == SPIFS_ENTRY_LENGTH);
|
|
STATIC_ASSERT(sizeof(toc_file_t) == SPIFS_ENTRY_LENGTH);
|
|
STATIC_ASSERT(sizeof(toc_footer_t) == SPIFS_ENTRY_LENGTH);
|
|
|
|
uint32_t page_size;
|
|
uint32_t page_count;
|
|
err = get_device_page_info(dev, &page_size, &page_count);
|
|
if (err != NO_ERROR)
|
|
return err;
|
|
|
|
// Make sure entries can be exactly packed into pages.
|
|
if (page_size % SPIFS_ENTRY_LENGTH != 0) {
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
// Make sure the device size is some multiple of the page size;
|
|
// we don't want a partial page at the end of the device.
|
|
if (dev->total_size % page_size != 0) {
|
|
return ERR_NOT_SUPPORTED;
|
|
}
|
|
|
|
uint32_t entires_per_page = page_size / SPIFS_ENTRY_LENGTH;
|
|
|
|
// Number of ToC entrries is the total number of entries less 2 for the
|
|
// header/footer
|
|
uint32_t num_entries = spifs_args->toc_pages * entires_per_page;
|
|
uint32_t num_toc_entries = num_entries - 2;
|
|
|
|
// Four entries will be consumed by metadata: Header, Front ToC entry,
|
|
// Back ToC entry, footer. If there are only four entries, there will be
|
|
// no room for files.
|
|
if (num_entries <= 4) {
|
|
return ERR_TOO_BIG;
|
|
}
|
|
|
|
// Create a mock spifs_t for the purposes of formatting the fs.
|
|
spifs_t spifs = {
|
|
.page_size = page_size,
|
|
.page_count = page_count,
|
|
.blocks_per_page = divpow2(page_size, dev->block_shift),
|
|
.generation = 1,
|
|
.num_entries = num_toc_entries,
|
|
.toc_position = FRONT_TOC,
|
|
.dev = dev,
|
|
};
|
|
spifs.page = memalign(CACHE_LINE, page_size);
|
|
list_initialize(&spifs.files);
|
|
list_initialize(&spifs.dcookies);
|
|
mutex_init(&spifs.lock);
|
|
|
|
spifs_file_t f_toc;
|
|
f_toc.metadata.page_idx = 0;
|
|
f_toc.metadata.length = spifs_args->toc_pages * page_size;
|
|
f_toc.metadata.capacity = spifs_args->toc_pages * page_size;
|
|
f_toc.fs_handle = &spifs;
|
|
memset(f_toc.metadata.filename, 0, MAX_FILENAME_LENGTH);
|
|
strlcpy(f_toc.metadata.filename, FRONT_TOC_LABEL, MAX_FILENAME_LENGTH);
|
|
|
|
spifs_file_t b_toc;
|
|
b_toc.metadata.page_idx = page_count - spifs_args->toc_pages;
|
|
b_toc.metadata.length = spifs_args->toc_pages * page_size;
|
|
b_toc.metadata.capacity = spifs_args->toc_pages * page_size;
|
|
b_toc.fs_handle = &spifs;
|
|
memset(b_toc.metadata.filename, 0, MAX_FILENAME_LENGTH);
|
|
strlcpy(b_toc.metadata.filename, BACK_TOC_LABEL, MAX_FILENAME_LENGTH);
|
|
|
|
spifs_add_ascending(&spifs, &f_toc);
|
|
spifs_add_ascending(&spifs, &b_toc);
|
|
|
|
// Commit the first toc.
|
|
err = spifs_commit_toc(&spifs);
|
|
if (err != NO_ERROR)
|
|
goto err;
|
|
|
|
// Commit the other toc.
|
|
err = spifs_commit_toc(&spifs);
|
|
if (err != NO_ERROR)
|
|
goto err;
|
|
|
|
err:
|
|
free(spifs.page);
|
|
|
|
return err;
|
|
}
|
|
|
|
static status_t spifs_mount(bdev_t *dev, fscookie **cookie)
|
|
{
|
|
status_t status;
|
|
|
|
LTRACEF("dev %p, cookie %p\n", dev, cookie);
|
|
|
|
spifs_t *spifs = malloc(sizeof(*spifs));
|
|
if (!spifs) {
|
|
return ERR_NO_MEMORY;
|
|
}
|
|
|
|
status = get_device_page_info(dev, &spifs->page_size, &spifs->page_count);
|
|
if (status != NO_ERROR) {
|
|
free(spifs);
|
|
return status;
|
|
}
|
|
|
|
spifs->blocks_per_page = divpow2(spifs->page_size, dev->block_shift);
|
|
|
|
spifs->page = memalign(CACHE_LINE, spifs->page_size);
|
|
if (!spifs->page) {
|
|
free(spifs);
|
|
return ERR_NO_MEMORY;
|
|
}
|
|
|
|
spifs->dev = dev;
|
|
|
|
list_initialize(&spifs->files);
|
|
list_initialize(&spifs->dcookies);
|
|
mutex_init(&spifs->lock);
|
|
|
|
// Determine which of the two Table of Contents we should use.
|
|
uint32_t f_toc_generation = get_toc_generation(spifs, FRONT_TOC);
|
|
uint32_t b_toc_generation = get_toc_generation(spifs, BACK_TOC);
|
|
|
|
if (f_toc_generation == CORRUPT_TOC && b_toc_generation == CORRUPT_TOC) {
|
|
// Both ToCs are corrupt.
|
|
status = ERR_CRC_FAIL;
|
|
goto err;
|
|
}
|
|
|
|
spifs->toc_position =
|
|
f_toc_generation > b_toc_generation ? FRONT_TOC : BACK_TOC;
|
|
spifs->generation = MAX(f_toc_generation, b_toc_generation);
|
|
|
|
uint32_t toc_page_addr = spifs->toc_position == FRONT_TOC ?
|
|
0 : spifs->page_count - 1;
|
|
|
|
cursor_t cursor;
|
|
status = cursor_init(&cursor, spifs, spifs->toc_position, toc_page_addr,
|
|
SPIFS_ENTRY_LENGTH);
|
|
if (status != NO_ERROR)
|
|
goto err;
|
|
|
|
toc_header_t *header = (toc_header_t*)cursor_get(&cursor);
|
|
spifs->num_entries = header->num_entries;
|
|
header = NULL;
|
|
|
|
// Create in-memory versions of metadata for files.
|
|
spifs_file_t *file;
|
|
for (size_t i = 0; i < spifs->num_entries; i++) {
|
|
status = cursor_advance(&cursor);
|
|
if (status != NO_ERROR)
|
|
goto err;
|
|
|
|
toc_file_t* file_entry = (toc_file_t*)cursor_get(&cursor);
|
|
if (file_entry->capacity == 0) {
|
|
continue;
|
|
}
|
|
|
|
file = malloc(sizeof(*file));
|
|
if (!file) {
|
|
status = ERR_NO_MEMORY;
|
|
goto err;
|
|
}
|
|
|
|
memcpy(&file->metadata, file_entry, SPIFS_ENTRY_LENGTH);
|
|
|
|
file->fs_handle = spifs;
|
|
|
|
list_add_tail(&spifs->files, &file->node);
|
|
}
|
|
|
|
if (!consistency_check(spifs)) {
|
|
status = ERR_BAD_STATE;
|
|
goto err;
|
|
}
|
|
|
|
*cookie = (fscookie *)spifs;
|
|
|
|
return NO_ERROR;
|
|
|
|
err:
|
|
while ((file = list_remove_head_type(&spifs->files, spifs_file_t, node))) {
|
|
free(file);
|
|
}
|
|
|
|
free(spifs->page);
|
|
free(spifs);
|
|
return status;
|
|
}
|
|
|
|
static status_t spifs_unmount(fscookie *cookie)
|
|
{
|
|
LTRACEF("cookie %p\n", cookie);
|
|
|
|
spifs_t *spifs = (spifs_t*)cookie;
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
spifs_file_t* file;
|
|
while ((file = list_remove_head_type(&spifs->files, spifs_file_t, node))) {
|
|
free(file);
|
|
}
|
|
|
|
free(spifs->page);
|
|
|
|
mutex_release(&spifs->lock);
|
|
|
|
free(spifs);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_create(fscookie *cookie, const char *name, filecookie **fcookie, uint64_t len)
|
|
{
|
|
status_t status = NO_ERROR;
|
|
|
|
LTRACEF("cookie %p name '%s' filecookie %p len %llu\n", cookie, name, fcookie, len);
|
|
|
|
spifs_t *spifs = (spifs_t*)cookie;
|
|
|
|
// Strip leading fwd-slashes
|
|
name = trim_name(name);
|
|
|
|
// File system is flat, directories not supported.
|
|
if (strchr(name, '/'))
|
|
return ERR_NOT_SUPPORTED;
|
|
|
|
// Check that filename is not too long.
|
|
if (strnlen(name, MAX_FILENAME_LENGTH) == MAX_FILENAME_LENGTH)
|
|
return ERR_BAD_PATH;
|
|
|
|
// Length is bigger than 4GB?
|
|
if (len > 0xFFFFFFFF)
|
|
return ERR_TOO_BIG;
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
if (find_file(spifs, name)) {
|
|
status = ERR_ALREADY_EXISTS;
|
|
goto err;
|
|
}
|
|
|
|
// Is the ToC full? Have we reached the limit on the number of files?
|
|
size_t num_files_in_toc = list_length(&spifs->files);
|
|
DEBUG_ASSERT(num_files_in_toc <= spifs->num_entries);
|
|
if (num_files_in_toc >= spifs->num_entries) {
|
|
status = ERR_TOO_BIG;
|
|
goto err;
|
|
}
|
|
|
|
uint32_t capacity;
|
|
if (len == 0) {
|
|
capacity = spifs->page_size;
|
|
} else {
|
|
capacity = ROUNDUP(len, spifs->page_size);
|
|
}
|
|
|
|
uint32_t open_run = find_open_run(spifs, capacity);
|
|
if (open_run == NO_OPEN_RUNS) {
|
|
status = ERR_TOO_BIG;
|
|
goto err;
|
|
}
|
|
|
|
spifs_file_t *file = malloc(sizeof(*file));
|
|
if (!file) {
|
|
status = ERR_NO_MEMORY;
|
|
goto err;
|
|
}
|
|
|
|
file->fs_handle = spifs;
|
|
file->metadata.page_idx = open_run;
|
|
file->metadata.length = len;
|
|
file->metadata.capacity = capacity;
|
|
memset(file->metadata.filename, 0, MAX_FILENAME_LENGTH);
|
|
strlcpy(file->metadata.filename, name, MAX_FILENAME_LENGTH);
|
|
|
|
// Erase the memory allocated to the file.
|
|
if (bio_erase(spifs->dev, open_run * spifs->page_size, capacity) !=
|
|
(ssize_t)capacity) {
|
|
|
|
free(file);
|
|
|
|
status = ERR_IO;
|
|
goto err;
|
|
}
|
|
|
|
spifs_add_ascending(spifs, file);
|
|
|
|
if (spifs_commit_toc(spifs) != NO_ERROR) {
|
|
// If the commit fails, make sure we don't leave any residue of the file
|
|
// lying around.
|
|
list_delete(&file->node);
|
|
free(file);
|
|
*fcookie = NULL;
|
|
|
|
status = ERR_IO;
|
|
goto err;
|
|
}
|
|
|
|
*fcookie = (filecookie*) file;
|
|
|
|
err:
|
|
mutex_release(&spifs->lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
static status_t spifs_open(fscookie *cookie, const char *name, filecookie **fcookie)
|
|
{
|
|
LTRACEF("cookie %p name '%s' filecookie %p\n", cookie, name, fcookie);
|
|
|
|
spifs_t *spifs = (spifs_t*)cookie;
|
|
|
|
name = trim_name(name);
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
spifs_file_t *file = find_file(spifs, name);
|
|
|
|
mutex_release(&spifs->lock);
|
|
|
|
if (!file)
|
|
return ERR_NOT_FOUND;
|
|
|
|
*fcookie = (filecookie *)file;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_close(filecookie *fcookie)
|
|
{
|
|
spifs_file_t *file = (spifs_file_t *)fcookie;
|
|
|
|
LTRACEF("cookie %p name '%s'\n", fcookie, file->metadata.filename);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_remove(fscookie *cookie, const char *name)
|
|
{
|
|
status_t status;
|
|
|
|
LTRACEF("cookie %p name '%s'\n", cookie, name);
|
|
|
|
spifs_t *spifs = (spifs_t *)cookie;
|
|
|
|
// make sure we strip out any leading /
|
|
name = trim_name(name);
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
spifs_file_t *file = find_file(spifs, name);
|
|
|
|
if (file) {
|
|
list_delete(&file->node);
|
|
free(file);
|
|
status = NO_ERROR;
|
|
} else {
|
|
status = ERR_NOT_FOUND;
|
|
goto err;
|
|
}
|
|
|
|
status = spifs_commit_toc(spifs);
|
|
|
|
err:
|
|
mutex_release(&spifs->lock);
|
|
|
|
return status;
|
|
}
|
|
|
|
static ssize_t spifs_read(filecookie *fcookie, void *buf, off_t off, size_t len)
|
|
{
|
|
LTRACEF("filecookie %p buf %p offset %lld len %zu\n", fcookie, buf, off, len);
|
|
|
|
spifs_file_t *file = (spifs_file_t*)fcookie;
|
|
spifs_t *spifs = file->fs_handle;
|
|
|
|
if (off < 0)
|
|
return ERR_INVALID_ARGS;
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
uint32_t file_start = file->fs_handle->page_size * file->metadata.page_idx;
|
|
uint32_t file_end = file_start + file->metadata.length;
|
|
|
|
uint32_t read_start = file_start + off;
|
|
uint32_t read_end = read_start + len;
|
|
|
|
if (read_start >= file_end) {
|
|
len = 0;
|
|
} else if (read_end > file_end) {
|
|
len = file_end - read_start;
|
|
}
|
|
|
|
DEBUG_ASSERT(file->fs_handle->dev);
|
|
|
|
ssize_t result = bio_read(file->fs_handle->dev, buf, read_start, len);
|
|
|
|
mutex_release(&spifs->lock);
|
|
|
|
return result;
|
|
}
|
|
|
|
static ssize_t spifs_write(filecookie *fcookie, const void *buf, off_t off, size_t size)
|
|
{
|
|
status_t err = NO_ERROR;
|
|
size_t len = size;
|
|
|
|
LTRACEF("filecookie %p buf %p offset %lld len %zu\n", fcookie, buf, off, len);
|
|
|
|
spifs_file_t *file = (spifs_file_t*)fcookie;
|
|
spifs_t *spifs = (spifs_t*)(file->fs_handle);
|
|
|
|
if (off < 0)
|
|
return ERR_INVALID_ARGS;
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
if (off + len > file->metadata.capacity) {
|
|
err = ERR_OUT_OF_RANGE;
|
|
goto err;
|
|
}
|
|
|
|
bool dirty_toc = false;
|
|
|
|
uint32_t start_addr =
|
|
off + (file->metadata.page_idx * spifs->page_size);
|
|
|
|
uint32_t page_shift = log2_uint(spifs->page_size);
|
|
uint32_t target_page_id = divpow2(start_addr, page_shift);
|
|
|
|
// Are we growing the file?
|
|
if (off + len > file->metadata.length) {
|
|
file->metadata.length = off + len;
|
|
dirty_toc = true;
|
|
}
|
|
|
|
// Leading Partial Page.
|
|
uint32_t page_offset = start_addr % spifs->page_size;
|
|
if (page_offset) {
|
|
uint32_t page_end = ROUNDUP(start_addr, spifs->page_size);
|
|
|
|
uint32_t n_bytes = MIN(len, page_end - start_addr);
|
|
|
|
// read..
|
|
err = spifs_read_page(spifs, target_page_id);
|
|
if (err != NO_ERROR) {
|
|
goto err;
|
|
}
|
|
|
|
// modify..
|
|
memcpy(spifs->page + page_offset, buf, n_bytes);
|
|
|
|
// write..
|
|
err = spifs_write_page(spifs, target_page_id);
|
|
if (err != NO_ERROR) {
|
|
goto err;
|
|
}
|
|
|
|
len -= n_bytes;
|
|
buf += n_bytes;
|
|
target_page_id++;
|
|
}
|
|
|
|
// Internal Full Pages.
|
|
while (len >= spifs->page_size) {
|
|
memcpy(spifs->page, buf, spifs->page_size);
|
|
err = spifs_write_page(spifs, target_page_id);
|
|
if (err != NO_ERROR) {
|
|
goto err;
|
|
}
|
|
|
|
len -= spifs->page_size;
|
|
buf += spifs->page_size;
|
|
target_page_id++;
|
|
}
|
|
|
|
// Trailing Partial Page.
|
|
if (len) { // Bytes remaining?
|
|
// read..
|
|
err = spifs_read_page(spifs, target_page_id);
|
|
if (err != NO_ERROR) {
|
|
goto err;
|
|
}
|
|
|
|
// modify..
|
|
memcpy(spifs->page, buf, len);
|
|
|
|
// write..
|
|
err = spifs_write_page(spifs, target_page_id);
|
|
if (err != NO_ERROR) {
|
|
goto err;
|
|
} else {
|
|
len = 0;
|
|
}
|
|
}
|
|
|
|
if (dirty_toc) {
|
|
err = spifs_commit_toc(spifs);
|
|
}
|
|
|
|
err:
|
|
mutex_release(&spifs->lock);
|
|
return len == 0 ? (ssize_t)size : err;
|
|
}
|
|
|
|
static status_t spifs_stat(filecookie *fcookie, struct file_stat *stat)
|
|
{
|
|
LTRACEF("filecookie %p stat %p\n", fcookie, stat);
|
|
|
|
spifs_file_t *file = (spifs_file_t*)fcookie;
|
|
|
|
mutex_acquire(&file->fs_handle->lock);
|
|
|
|
if (stat) {
|
|
stat->is_dir = false;
|
|
stat->size = file->metadata.length;
|
|
stat->capacity = file->metadata.capacity;
|
|
}
|
|
|
|
mutex_release(&file->fs_handle->lock);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_opendir(fscookie *cookie, const char *name, dircookie **dcookie)
|
|
{
|
|
LTRACEF("cookie %p name '%s' dircookie %p\n", cookie, name, dcookie);
|
|
|
|
spifs_t *spifs = (spifs_t *)cookie;
|
|
|
|
name = trim_name(name);
|
|
|
|
if (strcmp("", name))
|
|
return ERR_NOT_FOUND;
|
|
|
|
dircookie *dir = malloc(sizeof(*dir));
|
|
if (!dir)
|
|
return ERR_NO_MEMORY;
|
|
|
|
dir->fs = spifs;
|
|
|
|
mutex_acquire(&spifs->lock);
|
|
|
|
spifs_file_t *front_toc_file =
|
|
list_peek_head_type(&spifs->files, spifs_file_t, node);
|
|
dir->next_file = list_next_type(&spifs->files, &front_toc_file->node,
|
|
spifs_file_t, node);
|
|
list_add_head(&spifs->dcookies, &dir->node);
|
|
|
|
mutex_release(&spifs->lock);
|
|
|
|
*dcookie = dir;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_readdir(dircookie *dcookie, struct dirent *ent)
|
|
{
|
|
status_t err;
|
|
|
|
LTRACEF("dircookie %p ent %p\n", dcookie, ent);
|
|
|
|
mutex_acquire(&dcookie->fs->lock);
|
|
|
|
spifs_file_t *back_toc_file =
|
|
list_peek_tail_type(&dcookie->fs->files, spifs_file_t, node);
|
|
|
|
if (dcookie->next_file != back_toc_file) {
|
|
strlcpy(ent->name, dcookie->next_file->metadata.filename, sizeof(ent->name));
|
|
dcookie->next_file =
|
|
list_next_type(&dcookie->fs->files, &dcookie->next_file->node,
|
|
spifs_file_t, node);
|
|
err = NO_ERROR;
|
|
} else {
|
|
err = ERR_NOT_FOUND;
|
|
}
|
|
|
|
mutex_release(&dcookie->fs->lock);
|
|
|
|
return err;
|
|
}
|
|
|
|
static status_t spifs_closedir(dircookie *dcookie)
|
|
{
|
|
LTRACEF("dircookie %p\n", dcookie);
|
|
|
|
mutex_acquire(&dcookie->fs->lock);
|
|
list_delete(&dcookie->node);
|
|
mutex_release(&dcookie->fs->lock);
|
|
|
|
free(dcookie);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static status_t spifs_fs_stat(fscookie *cookie, struct fs_stat *stat)
|
|
{
|
|
LTRACEF("cookie %p, stat %p\n", cookie, stat);
|
|
|
|
spifs_t *spifs = (spifs_t*)cookie;
|
|
|
|
stat->total_space = (uint64_t)spifs->dev->total_size;
|
|
stat->free_space = stat->total_space - used_space(spifs);
|
|
|
|
stat->total_inodes = spifs->num_entries;
|
|
stat->free_inodes = stat->total_inodes - list_length(&spifs->files);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static const struct fs_api spifs_api = {
|
|
.format = spifs_format,
|
|
.fs_stat = spifs_fs_stat,
|
|
|
|
.mount = spifs_mount,
|
|
.unmount = spifs_unmount,
|
|
|
|
.create = spifs_create,
|
|
.open = spifs_open,
|
|
.remove = spifs_remove,
|
|
.close = spifs_close,
|
|
|
|
.read = spifs_read,
|
|
.write = spifs_write,
|
|
|
|
.stat = spifs_stat,
|
|
|
|
.opendir = spifs_opendir,
|
|
.readdir = spifs_readdir,
|
|
.closedir = spifs_closedir,
|
|
};
|
|
|
|
STATIC_FS_IMPL(spifs, &spifs_api);
|