blob: 7359a694de359f58c7d661bdfeb67b491b599d26 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "os/mynewt.h"
#include "bsp/bsp.h"
#include "hal/hal_flash.h"
#include "flash_map/flash_map.h"
#include "stats/stats.h"
#include "nffs_priv.h"
#include "nffs/nffs.h"
#include "fs/fs_if.h"
#include "disk/disk.h"
struct nffs_area *nffs_areas;
uint8_t nffs_num_areas;
uint8_t nffs_scratch_area_idx;
uint16_t nffs_block_max_data_sz;
struct nffs_area_desc *nffs_current_area_descs;
struct os_mempool nffs_file_pool;
struct os_mempool nffs_dir_pool;
struct os_mempool nffs_inode_entry_pool;
struct os_mempool nffs_block_entry_pool;
struct os_mempool nffs_cache_inode_pool;
struct os_mempool nffs_cache_block_pool;
void *nffs_file_mem;
void *nffs_inode_mem;
void *nffs_block_entry_mem;
void *nffs_cache_inode_mem;
void *nffs_cache_block_mem;
void *nffs_dir_mem;
struct nffs_inode_entry *nffs_root_dir;
struct nffs_inode_entry *nffs_lost_found_dir;
static struct os_mutex nffs_mutex;
static int nffs_open(const char *path, uint8_t access_flags,
struct fs_file **out_file);
static int nffs_close(struct fs_file *fs_file);
static int nffs_read(struct fs_file *fs_file, uint32_t len, void *out_data,
uint32_t *out_len);
static int nffs_write(struct fs_file *fs_file, const void *data, int len);
static int nffs_seek(struct fs_file *fs_file, uint32_t offset);
static uint32_t nffs_getpos(const struct fs_file *fs_file);
static int nffs_file_len(const struct fs_file *fs_file, uint32_t *out_len);
static int nffs_unlink(const char *path);
static int nffs_rename(const char *from, const char *to);
static int nffs_mkdir(const char *path);
static int nffs_opendir(const char *path, struct fs_dir **out_fs_dir);
static int nffs_readdir(struct fs_dir *dir, struct fs_dirent **out_dirent);
static int nffs_closedir(struct fs_dir *dir);
static int nffs_dirent_name(const struct fs_dirent *fs_dirent, size_t max_len,
char *out_name, uint8_t *out_name_len);
static int nffs_dirent_is_dir(const struct fs_dirent *fs_dirent);
struct fs_ops nffs_ops = {
.f_open = nffs_open,
.f_close = nffs_close,
.f_read = nffs_read,
.f_write = nffs_write,
.f_seek = nffs_seek,
.f_getpos = nffs_getpos,
.f_filelen = nffs_file_len,
.f_unlink = nffs_unlink,
.f_rename = nffs_rename,
.f_mkdir = nffs_mkdir,
.f_opendir = nffs_opendir,
.f_readdir = nffs_readdir,
.f_closedir = nffs_closedir,
.f_dirent_name = nffs_dirent_name,
.f_dirent_is_dir = nffs_dirent_is_dir,
.f_name = "nffs"
};
STATS_SECT_DECL(nffs_stats) nffs_stats;
STATS_NAME_START(nffs_stats)
STATS_NAME(nffs_stats, nffs_hashcnt_ins)
STATS_NAME(nffs_stats, nffs_hashcnt_rm)
STATS_NAME(nffs_stats, nffs_object_count)
STATS_NAME(nffs_stats, nffs_iocnt_read)
STATS_NAME(nffs_stats, nffs_iocnt_write)
STATS_NAME(nffs_stats, nffs_gccnt)
STATS_NAME(nffs_stats, nffs_readcnt_data)
STATS_NAME(nffs_stats, nffs_readcnt_block)
STATS_NAME(nffs_stats, nffs_readcnt_crc)
STATS_NAME(nffs_stats, nffs_readcnt_copy)
STATS_NAME(nffs_stats, nffs_readcnt_format)
STATS_NAME(nffs_stats, nffs_readcnt_gccollate)
STATS_NAME(nffs_stats, nffs_readcnt_inode)
STATS_NAME(nffs_stats, nffs_readcnt_inodeent)
STATS_NAME(nffs_stats, nffs_readcnt_rename)
STATS_NAME(nffs_stats, nffs_readcnt_update)
STATS_NAME(nffs_stats, nffs_readcnt_filename)
STATS_NAME(nffs_stats, nffs_readcnt_object)
STATS_NAME(nffs_stats, nffs_readcnt_detect)
STATS_NAME_END(nffs_stats)
static void
nffs_lock(void)
{
int rc;
rc = os_mutex_pend(&nffs_mutex, 0xffffffff);
assert(rc == 0 || rc == OS_NOT_STARTED);
}
static void
nffs_unlock(void)
{
int rc;
rc = os_mutex_release(&nffs_mutex);
assert(rc == 0 || rc == OS_NOT_STARTED);
}
static int
nffs_stats_init(void)
{
int rc;
rc = stats_init_and_reg(
STATS_HDR(nffs_stats),
STATS_SIZE_INIT_PARMS(nffs_stats, STATS_SIZE_32),
STATS_NAME_INIT_PARMS(nffs_stats),
"nffs_stats");
if (rc) {
if (rc < 0) {
/* multiple initializations are okay */
rc = 0;
} else {
rc = FS_EOS;
}
}
return rc;
}
/**
* Opens a file at the specified path. The result of opening a nonexistent
* file depends on the access flags specified. All intermediate directories
* must already exist.
*
* The mode strings passed to fopen() map to nffs_open()'s access flags as
* follows:
* "r" - FS_ACCESS_READ
* "r+" - FS_ACCESS_READ | FS_ACCESS_WRITE
* "w" - FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE
* "w+" - FS_ACCESS_READ | FS_ACCESS_WRITE | FS_ACCESS_TRUNCATE
* "a" - FS_ACCESS_WRITE | FS_ACCESS_APPEND
* "a+" - FS_ACCESS_READ | FS_ACCESS_WRITE | FS_ACCESS_APPEND
*
* @param path The path of the file to open.
* @param access_flags Flags controlling file access; see above table.
* @param out_file On success, a pointer to the newly-created file
* handle gets written here.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_open(const char *path, uint8_t access_flags, struct fs_file **out_fs_file)
{
int rc;
struct nffs_file *out_file;
char *filepath = NULL;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
filepath = disk_filepath_from_path(path);
rc = nffs_file_open(&out_file, filepath, access_flags);
if (rc != 0) {
goto done;
}
*out_fs_file = (struct fs_file *)out_file;
done:
if (filepath) {
free(filepath);
}
nffs_unlock();
if (rc != 0) {
*out_fs_file = NULL;
}
return rc;
}
/**
* Closes the specified file and invalidates the file handle. If the file has
* already been unlinked, and this is the last open handle to the file, this
* operation causes the file to be deleted from disk.
*
* @param file The file handle to close.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_close(struct fs_file *fs_file)
{
int rc;
struct nffs_file *file = (struct nffs_file *)fs_file;
if (file == NULL) {
return 0;
}
nffs_lock();
rc = nffs_file_close(file);
nffs_unlock();
return rc;
}
/**
* Positions a file's read and write pointer at the specified offset. The
* offset is expressed as the number of bytes from the start of the file (i.e.,
* seeking to offset 0 places the pointer at the first byte in the file).
*
* @param file The file to reposition.
* @param offset The 0-based file offset to seek to.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_seek(struct fs_file *fs_file, uint32_t offset)
{
int rc;
struct nffs_file *file = (struct nffs_file *)fs_file;
nffs_lock();
rc = nffs_file_seek(file, offset);
nffs_unlock();
return rc;
}
/**
* Retrieves the current read and write position of the specified open file.
*
* @param file The file to query.
*
* @return The file offset, in bytes.
*/
static uint32_t
nffs_getpos(const struct fs_file *fs_file)
{
uint32_t offset;
const struct nffs_file *file = (const struct nffs_file *)fs_file;
nffs_lock();
offset = file->nf_offset;
nffs_unlock();
return offset;
}
/**
* Retrieves the current length of the specified open file.
*
* @param file The file to query.
* @param out_len On success, the number of bytes in the file gets
* written here.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_file_len(const struct fs_file *fs_file, uint32_t *out_len)
{
int rc;
const struct nffs_file *file = (const struct nffs_file *)fs_file;
nffs_lock();
rc = nffs_inode_data_len(file->nf_inode_entry, out_len);
nffs_unlock();
return rc;
}
/**
* Reads data from the specified file. If more data is requested than remains
* in the file, all available data is retrieved and a success code is returned.
*
* @param file The file to read from.
* @param len The number of bytes to attempt to read.
* @param out_data The destination buffer to read into.
* @param out_len On success, the number of bytes actually read gets
* written here. Pass null if you don't care.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_read(struct fs_file *fs_file, uint32_t len, void *out_data,
uint32_t *out_len)
{
int rc;
struct nffs_file *file = (struct nffs_file *)fs_file;
nffs_lock();
rc = nffs_file_read(file, len, out_data, out_len);
nffs_unlock();
return rc;
}
/**
* Writes the supplied data to the current offset of the specified file handle.
*
* @param file The file to write to.
* @param data The data to write.
* @param len The number of bytes to write.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_write(struct fs_file *fs_file, const void *data, int len)
{
int rc;
struct nffs_file *file = (struct nffs_file *)fs_file;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
rc = nffs_write_to_file(file, data, len);
if (rc != 0) {
goto done;
}
rc = 0;
done:
nffs_unlock();
return rc;
}
/**
* Unlinks the file or directory at the specified path. If the path refers to
* a directory, all the directory's descendants are recursively unlinked. Any
* open file handles refering to an unlinked file remain valid, and can be
* read from and written to.
*
* @path The path of the file or directory to unlink.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_unlink(const char *path)
{
int rc;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
rc = nffs_path_unlink(path);
if (rc != 0) {
goto done;
}
rc = 0;
done:
nffs_unlock();
return rc;
}
/**
* Performs a rename and / or move of the specified source path to the
* specified destination. The source path can refer to either a file or a
* directory. All intermediate directories in the destination path must
* already exist. If the source path refers to a file, the destination path
* must contain a full filename path, rather than just the new parent
* directory. If an object already exists at the specified destination path,
* this function causes it to be unlinked prior to the rename (i.e., the
* destination gets clobbered).
*
* @param from The source path.
* @param to The destination path.
*
* @return 0 on success;
* nonzero on failure.
*/
static int
nffs_rename(const char *from, const char *to)
{
int rc;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
rc = nffs_path_rename(from, to);
if (rc != 0) {
goto done;
}
rc = 0;
done:
nffs_unlock();
return rc;
}
/**
* Creates the directory represented by the specified path. All intermediate
* directories must already exist. The specified path must start with a '/'
* character.
*
* @param path The name of the directory to create.
*
* @return 0 on success;
* nonzero on failure.
*/
static int
nffs_mkdir(const char *path)
{
int rc;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
rc = nffs_path_new_dir(path, NULL);
if (rc != 0) {
goto done;
}
done:
nffs_unlock();
return rc;
}
/**
* Opens the directory at the specified path. The directory's contents can be
* read with subsequent calls to nffs_readdir(). When you are done with the
* directory handle, close it with nffs_closedir().
*
* Unlinking files from the directory while it is open may result in
* unpredictable behavior. New files can be created inside the directory.
*
* @param path The name of the directory to open.
* @param out_dir On success, points to the directory handle.
*
* @return 0 on success;
* FS_ENOENT if the specified directory does not
* exist;
* other nonzero on error.
*/
static int
nffs_opendir(const char *path, struct fs_dir **out_fs_dir)
{
int rc;
struct nffs_dir **out_dir = (struct nffs_dir **)out_fs_dir;
char *filepath = NULL;
nffs_lock();
if (!nffs_misc_ready()) {
rc = FS_EUNINIT;
goto done;
}
filepath = disk_filepath_from_path(path);
rc = nffs_dir_open(filepath, out_dir);
done:
if (filepath) {
free(filepath);
}
nffs_unlock();
if (rc != 0) {
*out_dir = NULL;
}
return rc;
}
/**
* Reads the next entry in an open directory.
*
* @param dir The directory handle to read from.
* @param out_dirent On success, points to the next child entry in
* the specified directory.
*
* @return 0 on success;
* FS_ENOENT if there are no more entries in the
* parent directory;
* other nonzero on error.
*/
static int
nffs_readdir(struct fs_dir *fs_dir, struct fs_dirent **out_fs_dirent)
{
int rc;
struct nffs_dir *dir = (struct nffs_dir *)fs_dir;
struct nffs_dirent **out_dirent = (struct nffs_dirent **)out_fs_dirent;
nffs_lock();
rc = nffs_dir_read(dir, out_dirent);
nffs_unlock();
return rc;
}
/**
* Closes the specified directory handle.
*
* @param dir The name of the directory to close.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_closedir(struct fs_dir *fs_dir)
{
int rc;
struct nffs_dir *dir = (struct nffs_dir *)fs_dir;
nffs_lock();
rc = nffs_dir_close(dir);
nffs_unlock();
return rc;
}
/**
* Retrieves the filename of the specified directory entry. The retrieved
* filename is always null-terminated. To ensure enough space to hold the full
* filename plus a null-termintor, a destination buffer of size
* (NFFS_FILENAME_MAX_LEN + 1) should be used.
*
* @param dirent The directory entry to query.
* @param max_len The size of the "out_name" character buffer.
* @param out_name On success, the entry's filename is written
* here; always null-terminated.
* @param out_name_len On success, contains the actual length of the
* filename, NOT including the
* null-terminator.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_dirent_name(const struct fs_dirent *fs_dirent, size_t max_len,
char *out_name, uint8_t *out_name_len)
{
int rc;
struct nffs_dirent *dirent = (struct nffs_dirent *)fs_dirent;
nffs_lock();
assert(dirent != NULL && dirent->nde_inode_entry != NULL);
rc = nffs_inode_read_filename(dirent->nde_inode_entry, max_len, out_name,
out_name_len);
nffs_unlock();
return rc;
}
/**
* Tells you whether the specified directory entry is a sub-directory or a
* regular file.
*
* @param dirent The directory entry to query.
*
* @return 1: The entry is a directory;
* 0: The entry is a regular file.
*/
static int
nffs_dirent_is_dir(const struct fs_dirent *fs_dirent)
{
uint32_t id;
const struct nffs_dirent *dirent = (const struct nffs_dirent *)fs_dirent;
nffs_lock();
assert(dirent != NULL && dirent->nde_inode_entry != NULL);
id = dirent->nde_inode_entry->nie_hash_entry.nhe_id;
nffs_unlock();
return nffs_hash_id_is_dir(id);
}
/**
* Erases all the specified areas and initializes them with a clean nffs
* file system.
*
* @param area_descs The set of areas to format.
*
* @return 0 on success;
* nonzero on failure.
*/
int
nffs_format(const struct nffs_area_desc *area_descs)
{
int rc;
nffs_lock();
rc = nffs_format_full(area_descs);
nffs_unlock();
return rc;
}
/**
* Searches for a valid nffs file system among the specified areas. This
* function succeeds if a file system is detected among any subset of the
* supplied areas. If the area set does not contain a valid file system,
* a new one can be created via a separate call to nffs_format().
*
* @param area_descs The area set to search. This array must be
* terminated with a 0-length area.
*
* @return 0 on success;
* FS_ECORRUPT if no valid file system was detected;
* other nonzero on error.
*/
int
nffs_detect(const struct nffs_area_desc *area_descs)
{
int rc;
nffs_lock();
rc = nffs_restore_full(area_descs);
nffs_unlock();
return rc;
}
/**
* Initializes internal nffs memory and data structures. This must be called
* before any nffs operations are attempted.
*
* @return 0 on success; nonzero on error.
*/
int
nffs_init(void)
{
int rc;
nffs_config_init();
nffs_cache_clear();
rc = nffs_stats_init();
if (rc != 0) {
return FS_EOS;
}
rc = os_mutex_init(&nffs_mutex);
if (rc != 0) {
return FS_EOS;
}
free(nffs_file_mem);
nffs_file_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_files, sizeof (struct nffs_file)));
if (nffs_file_mem == NULL) {
return FS_ENOMEM;
}
free(nffs_inode_mem);
nffs_inode_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_inodes,
sizeof (struct nffs_inode_entry)));
if (nffs_inode_mem == NULL) {
return FS_ENOMEM;
}
free(nffs_block_entry_mem);
nffs_block_entry_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_blocks,
sizeof (struct nffs_hash_entry)));
if (nffs_block_entry_mem == NULL) {
return FS_ENOMEM;
}
free(nffs_cache_inode_mem);
nffs_cache_inode_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_cache_inodes,
sizeof (struct nffs_cache_inode)));
if (nffs_cache_inode_mem == NULL) {
return FS_ENOMEM;
}
free(nffs_cache_block_mem);
nffs_cache_block_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_cache_blocks,
sizeof (struct nffs_cache_block)));
if (nffs_cache_block_mem == NULL) {
return FS_ENOMEM;
}
free(nffs_dir_mem);
nffs_dir_mem = malloc(
OS_MEMPOOL_BYTES(nffs_config.nc_num_dirs,
sizeof (struct nffs_dir)));
if (nffs_dir_mem == NULL) {
return FS_ENOMEM;
}
rc = nffs_misc_reset();
if (rc != 0) {
return rc;
}
fs_register(&nffs_ops);
return 0;
}
void
nffs_pkg_init(void)
{
struct nffs_area_desc descs[MYNEWT_VAL(NFFS_NUM_AREAS) + 1];
int cnt;
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
/* Initialize nffs's internal state. */
rc = nffs_init();
SYSINIT_PANIC_ASSERT(rc == 0);
/* Convert the set of flash blocks we intend to use for nffs into an array
* of nffs area descriptors.
*/
cnt = MYNEWT_VAL(NFFS_NUM_AREAS);
rc = nffs_misc_desc_from_flash_area(
MYNEWT_VAL(NFFS_FLASH_AREA), &cnt, descs);
SYSINIT_PANIC_ASSERT(rc == 0);
/* Attempt to restore an existing nffs file system from flash. */
rc = nffs_detect(descs);
switch (rc) {
case 0:
break;
case FS_ECORRUPT:
/* No valid nffs instance detected; act based on configued detection
* failure policy.
*/
switch (MYNEWT_VAL(NFFS_DETECT_FAIL)) {
case NFFS_DETECT_FAIL_IGNORE:
break;
case NFFS_DETECT_FAIL_FORMAT:
rc = nffs_format(descs);
SYSINIT_PANIC_ASSERT(rc == 0);
break;
default:
SYSINIT_PANIC();
break;
}
break;
default:
SYSINIT_PANIC();
break;
}
}