blob: 69a2693e5cffcfe805b41e9e3a11eb629cb1ec6f [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 <stddef.h>
#include <string.h>
#include <assert.h>
#include "os/mynewt.h"
#include "testutil/testutil.h"
#include "nffs/nffs.h"
#include "nffs_priv.h"
/* Partition the flash buffer into two equal halves; used for filename
* comparisons.
*/
#define NFFS_INODE_FILENAME_BUF_SZ (sizeof nffs_flash_buf / 2)
static uint8_t *nffs_inode_filename_buf0 = nffs_flash_buf;
static uint8_t *nffs_inode_filename_buf1 =
nffs_flash_buf + NFFS_INODE_FILENAME_BUF_SZ;
/** A list of directory inodes with pending unlink operations. */
static struct nffs_hash_list nffs_inode_unlink_list;
struct nffs_inode_entry *
nffs_inode_entry_alloc(void)
{
struct nffs_inode_entry *inode_entry;
inode_entry = os_memblock_get(&nffs_inode_entry_pool);
if (inode_entry != NULL) {
memset(inode_entry, 0, sizeof *inode_entry);
}
return inode_entry;
}
void
nffs_inode_entry_free(struct nffs_inode_entry *inode_entry)
{
if (inode_entry != NULL) {
assert(!nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_INHASH));
assert(nffs_hash_id_is_inode(inode_entry->nie_hash_entry.nhe_id));
os_memblock_put(&nffs_inode_entry_pool, inode_entry);
}
}
/**
* Allocates a inode entry. If allocation fails due to memory exhaustion,
* garbage collection is performed and the allocation is retried. This
* process is repeated until allocation is successful or all areas have been
* garbage collected.
*
* @param out_inode_entry On success, the address of the allocated
* inode gets written here.
*
* @return 0 on successful allocation;
* FS_ENOMEM on memory exhaustion;
* other nonzero on garbage collection error.
*/
int
nffs_inode_entry_reserve(struct nffs_inode_entry **out_inode_entry)
{
int rc;
do {
*out_inode_entry = nffs_inode_entry_alloc();
} while (nffs_misc_gc_if_oom(*out_inode_entry, &rc));
return rc;
}
uint32_t
nffs_inode_disk_size(const struct nffs_inode *inode)
{
return sizeof (struct nffs_disk_inode) + inode->ni_filename_len;
}
int
nffs_inode_read_disk(uint8_t area_idx, uint32_t offset,
struct nffs_disk_inode *out_disk_inode)
{
int rc;
STATS_INC(nffs_stats, nffs_readcnt_inode);
rc = nffs_flash_read(area_idx, offset, out_disk_inode,
sizeof *out_disk_inode);
if (rc != 0) {
return rc;
}
if (!nffs_hash_id_is_inode(out_disk_inode->ndi_id)) {
return FS_EUNEXP;
}
return 0;
}
int
nffs_inode_write_disk(const struct nffs_disk_inode *disk_inode,
const char *filename, uint8_t area_idx,
uint32_t area_offset)
{
int rc;
/* Only the DELETED flag is ever written out to flash */
assert((disk_inode->ndi_flags & ~NFFS_INODE_FLAG_DELETED) == 0);
rc = nffs_flash_write(area_idx, area_offset, disk_inode,
sizeof *disk_inode);
if (rc != 0) {
return rc;
}
if (disk_inode->ndi_filename_len != 0) {
rc = nffs_flash_write(area_idx, area_offset + sizeof *disk_inode,
filename, disk_inode->ndi_filename_len);
if (rc != 0) {
return rc;
}
}
ASSERT_IF_TEST(nffs_crc_disk_inode_validate(disk_inode, area_idx,
area_offset) == 0);
return 0;
}
int
nffs_inode_calc_data_length(struct nffs_inode_entry *inode_entry,
uint32_t *out_len)
{
struct nffs_hash_entry *cur;
struct nffs_block block;
int rc;
assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
*out_len = 0;
inode_entry->nie_blkcnt = 0;
cur = inode_entry->nie_last_block_entry;
while (cur != NULL) {
rc = nffs_block_from_hash_entry(&block, cur);
if (rc != 0) {
return rc;
}
*out_len += block.nb_data_len;
inode_entry->nie_blkcnt++;
cur = block.nb_prev;
}
return 0;
}
int
nffs_inode_data_len(struct nffs_inode_entry *inode_entry, uint32_t *out_len)
{
struct nffs_cache_inode *cache_inode;
int rc;
rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
if (rc != 0) {
return rc;
}
*out_len = cache_inode->nci_file_size;
return 0;
}
static void
nffs_inode_restore_from_dummy_entry(struct nffs_inode *out_inode,
struct nffs_inode_entry *inode_entry)
{
memset(out_inode, 0, sizeof *out_inode);
out_inode->ni_inode_entry = inode_entry;
}
int
nffs_inode_from_entry(struct nffs_inode *out_inode,
struct nffs_inode_entry *entry)
{
struct nffs_disk_inode disk_inode;
uint32_t area_offset;
uint8_t area_idx;
int cached_name_len;
int rc;
memset(out_inode, 0, sizeof *out_inode);
if (nffs_inode_is_dummy(entry)) {
nffs_inode_restore_from_dummy_entry(out_inode, entry);
return FS_ENOENT;
}
nffs_flash_loc_expand(entry->nie_hash_entry.nhe_flash_loc,
&area_idx, &area_offset);
rc = nffs_inode_read_disk(area_idx, area_offset, &disk_inode);
if (rc != 0) {
return rc;
}
out_inode->ni_inode_entry = entry;
out_inode->ni_seq = disk_inode.ndi_seq;
/*
* Relink to parent if possible
* XXX does this belong here?
*/
if (disk_inode.ndi_parent_id == NFFS_ID_NONE) {
out_inode->ni_parent = NULL;
} else {
out_inode->ni_parent = nffs_hash_find_inode(disk_inode.ndi_parent_id);
}
out_inode->ni_filename_len = disk_inode.ndi_filename_len;
if (out_inode->ni_filename_len > NFFS_SHORT_FILENAME_LEN) {
cached_name_len = NFFS_SHORT_FILENAME_LEN;
} else {
cached_name_len = out_inode->ni_filename_len;
}
if (cached_name_len != 0) {
STATS_INC(nffs_stats, nffs_readcnt_inodeent);
rc = nffs_flash_read(area_idx, area_offset + sizeof disk_inode,
out_inode->ni_filename, cached_name_len);
if (rc != 0) {
return rc;
}
}
if (disk_inode.ndi_flags & NFFS_INODE_FLAG_DELETED) {
nffs_inode_setflags(out_inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
nffs_inode_setflags(entry, NFFS_INODE_FLAG_DELETED);
}
return 0;
}
uint32_t
nffs_inode_parent_id(const struct nffs_inode *inode)
{
if (inode->ni_parent == NULL) {
return NFFS_ID_NONE;
} else {
return inode->ni_parent->nie_hash_entry.nhe_id;
}
}
static int
nffs_inode_delete_blocks_from_ram(struct nffs_inode_entry *inode_entry)
{
int rc;
assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
while (inode_entry->nie_last_block_entry != NULL) {
rc = nffs_block_delete_from_ram(inode_entry->nie_last_block_entry);
if (rc != 0) {
return rc;
}
}
return 0;
}
/**
* Deletes the specified inode entry from the RAM representation.
*
* @param inode_entry The inode entry to delete.
* @param ignore_corruption Whether to proceed, despite file system
* corruption errors (0/1). This should only
* be used when the file system is in a known
* bad state (e.g., during the sweep phase of
* the restore process). If corruption
* detected and ignored, this function deletes
* what it can and returns a success code.
*
* @return 0 on success; nonzero on failure.
*/
static int
nffs_inode_delete_from_ram(struct nffs_inode_entry *inode_entry,
int ignore_corruption)
{
int rc;
if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
/*
* Record the intention to delete the file
*/
nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DELETED);
rc = nffs_inode_delete_blocks_from_ram(inode_entry);
if (rc == FS_ECORRUPT && ignore_corruption) {
inode_entry->nie_last_block_entry = NULL;
} else if (rc != 0) {
return rc;
}
}
nffs_cache_inode_delete(inode_entry);
/*
* XXX Not deleting empty inode delete records from hash could prevent
* a case where we could lose delete records in a gc operation
*/
nffs_hash_remove(&inode_entry->nie_hash_entry);
nffs_inode_entry_free(inode_entry);
return 0;
}
/**
* Inserts the specified inode entry into the unlink list. Because a hash
* entry only has a single 'next' pointer, this function removes the entry from
* the hash table prior to inserting it into the unlink list.
*
* @param inode_entry The inode entry to insert.
*/
static void
nffs_inode_insert_unlink_list(struct nffs_inode_entry *inode_entry)
{
nffs_hash_remove(&inode_entry->nie_hash_entry);
SLIST_INSERT_HEAD(&nffs_inode_unlink_list, &inode_entry->nie_hash_entry,
nhe_next);
}
static int
nffs_inode_dec_refcnt_priv(struct nffs_inode_entry *inode_entry,
int ignore_corruption)
{
int rc;
assert(inode_entry);
assert(inode_entry->nie_refcnt > 0);
inode_entry->nie_refcnt--;
if (inode_entry->nie_refcnt == 0) {
if (nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id)) {
rc = nffs_inode_delete_from_ram(inode_entry, ignore_corruption);
if (rc != 0) {
return rc;
}
} else {
nffs_inode_insert_unlink_list(inode_entry);
}
}
return 0;
}
int
nffs_inode_inc_refcnt(struct nffs_inode_entry *inode_entry)
{
assert(inode_entry);
inode_entry->nie_refcnt++;
return 0;
}
/**
* Decrements the reference count of the specified inode entry.
*
* @param inode_entry The inode entry whose reference count
* should be decremented.
*/
int
nffs_inode_dec_refcnt(struct nffs_inode_entry *inode_entry)
{
int rc;
rc = nffs_inode_dec_refcnt_priv(inode_entry, 0);
return rc;
}
/**
* Unlinks every directory inode entry present in the unlink list. After this
* function completes:
* o Each descendant directory is deleted from RAM.
* o Each descendant file has its reference count decremented (and deleted
* from RAM if its reference count reaches zero).
*
* @param inout_next This parameter is only necessary if you are
* calling this function during an iteration
* of the entire hash table; pass null
* otherwise.
* On input, this points to the next hash entry
* you plan on processing.
* On output, this points to the next hash entry
* that should be processed.
*/
static int
nffs_inode_process_unlink_list(struct nffs_hash_entry **inout_next,
int ignore_corruption)
{
struct nffs_inode_entry *inode_entry;
struct nffs_inode_entry *child_next;
struct nffs_inode_entry *child;
struct nffs_hash_entry *hash_entry;
int rc;
while ((hash_entry = SLIST_FIRST(&nffs_inode_unlink_list)) != NULL) {
assert(nffs_hash_id_is_dir(hash_entry->nhe_id));
SLIST_REMOVE_HEAD(&nffs_inode_unlink_list, nhe_next);
inode_entry = (struct nffs_inode_entry *)hash_entry;
/* Recursively unlink each child. */
child = SLIST_FIRST(&inode_entry->nie_child_list);
while (child != NULL) {
child_next = SLIST_NEXT(child, nie_sibling_next);
if (inout_next != NULL && *inout_next == &child->nie_hash_entry) {
*inout_next = &child_next->nie_hash_entry;
}
rc = nffs_inode_dec_refcnt_priv(child, ignore_corruption);
if (rc != 0) {
return rc;
}
child = child_next;
}
/* The directory is already removed from the hash table; just free its
* memory.
*/
nffs_inode_entry_free(inode_entry);
}
return 0;
}
int
nffs_inode_delete_from_disk(struct nffs_inode *inode)
{
struct nffs_disk_inode disk_inode;
uint32_t offset;
uint8_t area_idx;
int rc;
/* Make sure it isn't already deleted. */
assert(inode->ni_parent != NULL);
rc = nffs_misc_reserve_space(sizeof disk_inode, &area_idx, &offset);
if (rc != 0) {
return rc;
}
inode->ni_seq++;
memset(&disk_inode, 0, sizeof disk_inode);
disk_inode.ndi_id = inode->ni_inode_entry->nie_hash_entry.nhe_id;
disk_inode.ndi_seq = inode->ni_seq;
disk_inode.ndi_parent_id = NFFS_ID_NONE;
disk_inode.ndi_flags = NFFS_INODE_FLAG_DELETED;
if (inode->ni_inode_entry->nie_last_block_entry) {
disk_inode.ndi_lastblock_id =
inode->ni_inode_entry->nie_last_block_entry->nhe_id;
} else {
disk_inode.ndi_lastblock_id = NFFS_ID_NONE;
}
disk_inode.ndi_filename_len = 0;
nffs_crc_disk_inode_fill(&disk_inode, "");
rc = nffs_inode_write_disk(&disk_inode, "", area_idx, offset);
NFFS_LOG(DEBUG, "inode_del_disk: wrote unlinked ino %x to disk ref %d\n",
(unsigned int)disk_inode.ndi_id,
inode->ni_inode_entry->nie_refcnt);
/*
* Flag the incore inode as deleted to the inode won't get updated to
* disk. This could happen if the refcnt > 0 and there are future appends
* XXX only do this for files and not directories
*/
if (nffs_hash_id_is_file(inode->ni_inode_entry->nie_hash_entry.nhe_id)) {
nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
NFFS_LOG(DEBUG, "inode_delete_from_disk: ino %x flag DELETE\n",
(unsigned int)inode->ni_inode_entry->nie_hash_entry.nhe_id);
}
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Determines if an inode is an ancestor of another.
*
* NOTE: If the two inodes are equal, a positive result is indicated.
*
* @param anc The potential ancestor.
* @param des The potential descendent.
* @param out_is_ancestor On success, the result gets written here
* (1 if an ancestor relationship is
* detected).
*
* @return 0 on success; FS_E[...] on error.
*/
static int
nffs_inode_is_ancestor(struct nffs_inode_entry *anc,
struct nffs_inode_entry *des,
int *out_is_ancestor)
{
struct nffs_inode_entry *cur;
struct nffs_inode inode;
int rc;
/* Only directories can be ancestors. */
if (!nffs_hash_id_is_dir(anc->nie_hash_entry.nhe_id)) {
*out_is_ancestor = 0;
return 0;
}
/* Trace des's heritage until we hit anc or there are no more parents. */
cur = des;
while (cur != NULL && cur != anc) {
rc = nffs_inode_from_entry(&inode, cur);
if (rc != 0) {
*out_is_ancestor = 0;
return rc;
}
cur = inode.ni_parent;
}
*out_is_ancestor = cur == anc;
return 0;
}
int
nffs_inode_rename(struct nffs_inode_entry *inode_entry,
struct nffs_inode_entry *new_parent,
const char *new_filename)
{
struct nffs_disk_inode disk_inode;
struct nffs_inode inode;
uint32_t area_offset;
uint8_t area_idx;
int filename_len;
int ancestor;
int rc;
/* Don't allow a directory to be moved into a descendent directory. */
rc = nffs_inode_is_ancestor(inode_entry, new_parent, &ancestor);
if (rc != 0) {
return rc;
}
if (ancestor) {
return FS_EINVAL;
}
rc = nffs_inode_from_entry(&inode, inode_entry);
if (rc != 0) {
return rc;
}
if (inode.ni_parent != new_parent) {
if (inode.ni_parent != NULL) {
nffs_inode_remove_child(&inode);
}
if (new_parent != NULL) {
rc = nffs_inode_add_child(new_parent, inode.ni_inode_entry);
if (rc != 0) {
return rc;
}
}
inode.ni_parent = new_parent;
}
if (new_filename != NULL) {
filename_len = strlen(new_filename);
} else {
filename_len = inode.ni_filename_len;
nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
&area_idx, &area_offset);
STATS_INC(nffs_stats, nffs_readcnt_rename);
rc = nffs_flash_read(area_idx,
area_offset + sizeof (struct nffs_disk_inode),
nffs_flash_buf, filename_len);
if (rc != 0) {
return rc;
}
new_filename = (char *)nffs_flash_buf;
}
rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len,
&area_idx, &area_offset);
if (rc != 0) {
return rc;
}
memset(&disk_inode, 0, sizeof disk_inode);
disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id;
disk_inode.ndi_seq = inode.ni_seq + 1;
disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode);
disk_inode.ndi_flags = 0;
disk_inode.ndi_filename_len = filename_len;
if (inode_entry->nie_last_block_entry &&
inode_entry->nie_last_block_entry->nhe_id != NFFS_ID_NONE)
disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id;
else
disk_inode.ndi_lastblock_id = NFFS_ID_NONE;
nffs_crc_disk_inode_fill(&disk_inode, new_filename);
rc = nffs_inode_write_disk(&disk_inode, new_filename, area_idx,
area_offset);
if (rc != 0) {
return rc;
}
inode_entry->nie_hash_entry.nhe_flash_loc =
nffs_flash_loc(area_idx, area_offset);
return 0;
}
int
nffs_inode_update(struct nffs_inode_entry *inode_entry)
{
struct nffs_disk_inode disk_inode;
struct nffs_inode inode;
uint32_t area_offset;
uint8_t area_idx;
char *filename;
int filename_len;
int rc;
rc = nffs_inode_from_entry(&inode, inode_entry);
/*
* if rc == FS_ENOENT, file is dummy is unlinked and so
* can not be updated to disk.
*/
if (rc == FS_ENOENT)
assert(nffs_inode_is_dummy(inode_entry));
if (rc != 0) {
return rc;
}
assert(inode_entry->nie_hash_entry.nhe_flash_loc != NFFS_FLASH_LOC_NONE);
filename_len = inode.ni_filename_len;
nffs_flash_loc_expand(inode_entry->nie_hash_entry.nhe_flash_loc,
&area_idx, &area_offset);
STATS_INC(nffs_stats, nffs_readcnt_update);
rc = nffs_flash_read(area_idx,
area_offset + sizeof (struct nffs_disk_inode),
nffs_flash_buf, filename_len);
if (rc != 0) {
return rc;
}
filename = (char *)nffs_flash_buf;
rc = nffs_misc_reserve_space(sizeof disk_inode + filename_len,
&area_idx, &area_offset);
if (rc != 0) {
return rc;
}
memset(&disk_inode, 0, sizeof disk_inode);
disk_inode.ndi_id = inode_entry->nie_hash_entry.nhe_id;
disk_inode.ndi_seq = inode.ni_seq + 1;
disk_inode.ndi_parent_id = nffs_inode_parent_id(&inode);
disk_inode.ndi_flags = 0;
disk_inode.ndi_filename_len = filename_len;
assert(nffs_hash_id_is_block(inode_entry->nie_last_block_entry->nhe_id));
disk_inode.ndi_lastblock_id = inode_entry->nie_last_block_entry->nhe_id;
nffs_crc_disk_inode_fill(&disk_inode, filename);
NFFS_LOG(DEBUG, "nffs_inode_update writing inode %x last block %x\n",
(unsigned int)disk_inode.ndi_id,
(unsigned int)disk_inode.ndi_lastblock_id);
rc = nffs_inode_write_disk(&disk_inode, filename, area_idx,
area_offset);
if (rc != 0) {
return rc;
}
inode_entry->nie_hash_entry.nhe_flash_loc =
nffs_flash_loc(area_idx, area_offset);
return 0;
}
static int
nffs_inode_read_filename_chunk(const struct nffs_inode *inode,
uint8_t filename_offset, void *buf, int len)
{
uint32_t area_offset;
uint8_t area_idx;
int rc;
assert(filename_offset + len <= inode->ni_filename_len);
nffs_flash_loc_expand(inode->ni_inode_entry->nie_hash_entry.nhe_flash_loc,
&area_idx, &area_offset);
area_offset += sizeof (struct nffs_disk_inode) + filename_offset;
STATS_INC(nffs_stats, nffs_readcnt_filename);
rc = nffs_flash_read(area_idx, area_offset, buf, len);
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Retrieves the filename of the specified inode. 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 inode_entry The inode 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.
*/
int
nffs_inode_read_filename(struct nffs_inode_entry *inode_entry, size_t max_len,
char *out_name, uint8_t *out_full_len)
{
struct nffs_inode inode;
int read_len;
int rc;
rc = nffs_inode_from_entry(&inode, inode_entry);
if (rc != 0) {
return rc;
}
if (max_len > inode.ni_filename_len) {
read_len = inode.ni_filename_len;
} else {
read_len = max_len - 1;
}
rc = nffs_inode_read_filename_chunk(&inode, 0, out_name, read_len);
if (rc != 0) {
return rc;
}
out_name[read_len] = '\0';
return 0;
}
int
nffs_inode_add_child(struct nffs_inode_entry *parent,
struct nffs_inode_entry *child)
{
struct nffs_inode_entry *prev;
struct nffs_inode_entry *cur;
struct nffs_inode child_inode;
struct nffs_inode cur_inode;
int cmp;
int rc;
assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id));
assert(!nffs_inode_getflags(child, NFFS_INODE_FLAG_INTREE));
rc = nffs_inode_from_entry(&child_inode, child);
if (rc != 0) {
return rc;
}
prev = NULL;
SLIST_FOREACH(cur, &parent->nie_child_list, nie_sibling_next) {
assert(cur != child);
rc = nffs_inode_from_entry(&cur_inode, cur);
if (rc != 0) {
return rc;
}
rc = nffs_inode_filename_cmp_flash(&child_inode, &cur_inode, &cmp);
if (rc != 0) {
return rc;
}
if (cmp < 0) {
break;
}
prev = cur;
}
if (prev == NULL) {
SLIST_INSERT_HEAD(&parent->nie_child_list, child, nie_sibling_next);
} else {
SLIST_INSERT_AFTER(prev, child, nie_sibling_next);
}
nffs_inode_setflags(child, NFFS_INODE_FLAG_INTREE);
return 0;
}
void
nffs_inode_remove_child(struct nffs_inode *child)
{
struct nffs_inode_entry *parent;
assert(nffs_inode_getflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE));
parent = child->ni_parent;
assert(parent != NULL);
assert(nffs_hash_id_is_dir(parent->nie_hash_entry.nhe_id));
SLIST_REMOVE(&parent->nie_child_list, child->ni_inode_entry,
nffs_inode_entry, nie_sibling_next);
SLIST_NEXT(child->ni_inode_entry, nie_sibling_next) = NULL;
nffs_inode_unsetflags(child->ni_inode_entry, NFFS_INODE_FLAG_INTREE);
}
int
nffs_inode_filename_cmp_ram(const struct nffs_inode *inode,
const char *name, int name_len,
int *result)
{
int short_len;
int chunk_len;
int rem_len;
int off;
int rc;
if (name_len < inode->ni_filename_len) {
short_len = name_len;
} else {
short_len = inode->ni_filename_len;
}
if (short_len <= NFFS_SHORT_FILENAME_LEN) {
chunk_len = short_len;
} else {
chunk_len = NFFS_SHORT_FILENAME_LEN;
}
*result = strncmp((char *)inode->ni_filename, name, chunk_len);
off = chunk_len;
while (*result == 0 && off < short_len) {
rem_len = short_len - off;
if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) {
chunk_len = NFFS_INODE_FILENAME_BUF_SZ;
} else {
chunk_len = rem_len;
}
rc = nffs_inode_read_filename_chunk(inode, off,
nffs_inode_filename_buf0,
chunk_len);
if (rc != 0) {
return rc;
}
*result = strncmp((char *)nffs_inode_filename_buf0, name + off,
chunk_len);
off += chunk_len;
}
if (*result == 0) {
*result = inode->ni_filename_len - name_len;
}
return 0;
}
/*
* Compare filenames in flash
*/
int
nffs_inode_filename_cmp_flash(const struct nffs_inode *inode1,
const struct nffs_inode *inode2,
int *result)
{
int short_len;
int chunk_len;
int rem_len;
int off;
int rc;
if (inode1->ni_filename_len < inode2->ni_filename_len) {
short_len = inode1->ni_filename_len;
} else {
short_len = inode2->ni_filename_len;
}
if (short_len <= NFFS_SHORT_FILENAME_LEN) {
chunk_len = short_len;
} else {
chunk_len = NFFS_SHORT_FILENAME_LEN;
}
*result = strncmp((char *)inode1->ni_filename,
(char *)inode2->ni_filename,
chunk_len);
off = chunk_len;
while (*result == 0 && off < short_len) {
rem_len = short_len - off;
if (rem_len > NFFS_INODE_FILENAME_BUF_SZ) {
chunk_len = NFFS_INODE_FILENAME_BUF_SZ;
} else {
chunk_len = rem_len;
}
rc = nffs_inode_read_filename_chunk(inode1, off,
nffs_inode_filename_buf0,
chunk_len);
if (rc != 0) {
return rc;
}
rc = nffs_inode_read_filename_chunk(inode2, off,
nffs_inode_filename_buf1,
chunk_len);
if (rc != 0) {
return rc;
}
*result = strncmp((char *)nffs_inode_filename_buf0,
(char *)nffs_inode_filename_buf1,
chunk_len);
off += chunk_len;
}
if (*result == 0) {
*result = inode1->ni_filename_len - inode2->ni_filename_len;
}
return 0;
}
/**
* Finds the set of blocks composing the specified address range within the
* supplied file inode. This information is returned in the form of a
* seek_info object.
*
* @param inode_entry The file inode to seek within.
* @param offset The start address of the region to seek to.
* @param length The length of the region to seek to.
* @param out_seek_info On success, this gets populated with the result
* of the seek operation.
*
* @return 0 on success; nonzero on failure.
*/
int
nffs_inode_seek(struct nffs_inode_entry *inode_entry, uint32_t offset,
uint32_t length, struct nffs_seek_info *out_seek_info)
{
struct nffs_cache_inode *cache_inode;
struct nffs_hash_entry *cur_entry;
struct nffs_block block;
uint32_t block_start;
uint32_t cur_offset;
uint32_t seek_end;
int rc;
assert(length > 0);
assert(nffs_hash_id_is_file(inode_entry->nie_hash_entry.nhe_id));
rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
if (rc != 0) {
return rc;
}
if (offset > cache_inode->nci_file_size) {
return FS_EOFFSET;
}
if (offset == cache_inode->nci_file_size) {
memset(&out_seek_info->nsi_last_block, 0,
sizeof out_seek_info->nsi_last_block);
out_seek_info->nsi_last_block.nb_hash_entry = NULL;
out_seek_info->nsi_block_file_off = 0;
out_seek_info->nsi_file_len = cache_inode->nci_file_size;
return 0;
}
seek_end = offset + length;
cur_entry = inode_entry->nie_last_block_entry;
cur_offset = cache_inode->nci_file_size;
while (1) {
rc = nffs_block_from_hash_entry(&block, cur_entry);
if (rc != 0) {
return rc;
}
block_start = cur_offset - block.nb_data_len;
if (seek_end > block_start) {
out_seek_info->nsi_last_block = block;
out_seek_info->nsi_block_file_off = block_start;
out_seek_info->nsi_file_len = cache_inode->nci_file_size;
return 0;
}
cur_offset = block_start;
cur_entry = block.nb_prev;
}
}
/**
* Reads data from the specified file inode.
*
* @param inode_entry The inode to read from.
* @param offset The offset within the file to start the read
* at.
* @param len The number of bytes to attempt to read.
* @param out_data On success, the read data gets written here.
* @param out_len On success, the number of bytes actually read
* gets written here.
*
* @return 0 on success; nonzero on failure.
*/
int
nffs_inode_read(struct nffs_inode_entry *inode_entry, uint32_t offset,
uint32_t len, void *out_data, uint32_t *out_len)
{
struct nffs_cache_inode *cache_inode;
struct nffs_cache_block *cache_block;
uint32_t block_end;
uint32_t dst_off;
uint32_t src_off;
uint32_t src_end;
uint16_t block_off;
uint16_t chunk_sz;
uint8_t *dptr;
int rc;
if (len == 0) {
if (out_len != NULL) {
*out_len = 0;
}
return 0;
}
rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
if (rc != 0) {
return rc;
}
src_end = offset + len;
if (src_end > cache_inode->nci_file_size) {
src_end = cache_inode->nci_file_size;
}
/* Initialize variables for the first iteration. */
dst_off = src_end - offset;
src_off = src_end;
dptr = out_data;
cache_block = NULL;
/* Read each relevant block into the destination buffer, iterating in
* reverse.
*/
while (dst_off > 0) {
if (cache_block == NULL) {
rc = nffs_cache_seek(cache_inode, src_off - 1, &cache_block);
if (rc != 0) {
return rc;
}
}
if (cache_block->ncb_file_offset < offset) {
block_off = offset - cache_block->ncb_file_offset;
} else {
block_off = 0;
}
block_end = cache_block->ncb_file_offset +
cache_block->ncb_block.nb_data_len;
chunk_sz = cache_block->ncb_block.nb_data_len - block_off;
if (block_end > src_end) {
chunk_sz -= block_end - src_end;
}
dst_off -= chunk_sz;
src_off -= chunk_sz;
rc = nffs_block_read_data(&cache_block->ncb_block, block_off, chunk_sz,
dptr + dst_off);
if (rc != 0) {
return rc;
}
cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, ncb_link);
}
if (out_len != NULL) {
*out_len = src_end - offset;
}
return 0;
}
static int
nffs_inode_unlink_from_ram_priv(struct nffs_inode *inode,
int ignore_corruption,
struct nffs_hash_entry **out_next)
{
int rc;
if (inode->ni_parent != NULL) {
nffs_inode_remove_child(inode);
}
/*
* Regardless of whether the inode is removed from hashlist, we record
* the intention to delete it here.
*/
nffs_inode_setflags(inode->ni_inode_entry, NFFS_INODE_FLAG_DELETED);
if (nffs_hash_id_is_dir(inode->ni_inode_entry->nie_hash_entry.nhe_id)) {
nffs_inode_insert_unlink_list(inode->ni_inode_entry);
rc = nffs_inode_process_unlink_list(out_next, ignore_corruption);
} else {
rc = nffs_inode_dec_refcnt_priv(inode->ni_inode_entry,
ignore_corruption);
}
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Unlinks the specified inode from the RAM representation. If this procedure
* causes the inode's reference count to drop to zero, the inode is deleted
* from RAM. This function does not write anything to disk.
*
* @param inode The inode to unlink.
* @param out_next This parameter is only necessary if you are
* calling this function during an iteration
* of the entire hash table; pass null
* otherwise.
* On output, this points to the next hash entry
* that should be processed.
*
* @return 0 on success; nonzero on failure.
*/
int
nffs_inode_unlink_from_ram(struct nffs_inode *inode,
struct nffs_hash_entry **out_next)
{
int rc;
rc = nffs_inode_unlink_from_ram_priv(inode, 0, out_next);
return rc;
}
/**
* Deletes the specified inode entry from the RAM representation. If file
* system corruption is detected, this function completes as much of the unlink
* process as it cam and returns a success code. This should only be used when
* the file system is in a known bad state (e.g., during the sweep phase of the
* restore process).
*
* @param inode_entry The inode entry to delete.
* @param out_next This parameter is only necessary if you are
* calling this function during an iteration
* of the entire hash table; pass null
* otherwise.
* On output, this points to the next hash entry
* that should be processed.
*
* @return 0 on success; nonzero on failure.
*/
int
nffs_inode_unlink_from_ram_corrupt_ok(struct nffs_inode *inode,
struct nffs_hash_entry **out_next)
{
int rc;
rc = nffs_inode_unlink_from_ram_priv(inode, 1, out_next);
return rc;
}
/**
* Unlinks the file or directory represented by the specified inode. If the
* inode represents 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.
*
* When an inode is unlinked, the following events occur:
* o inode deletion record is written to disk.
* o inode is removed from parent's child list.
* o inode's reference count is decreased (if this brings it to zero, the
* inode is fully deleted from RAM).
*
* @param inode The inode to unlink.
*
* @return 0 on success; nonzero on failure.
*/
int
nffs_inode_unlink(struct nffs_inode *inode)
{
int rc;
rc = nffs_inode_delete_from_disk(inode);
if (rc != 0) {
return rc;
}
rc = nffs_inode_unlink_from_ram(inode, NULL);
if (rc != 0) {
return rc;
}
return 0;
}
/*
* Return true if inode is a dummy inode, that was allocated as a
* place holder in the case that an inode is restored before it's parent.
*/
int
nffs_inode_is_dummy(struct nffs_inode_entry *inode_entry)
{
if (inode_entry->nie_flash_loc == NFFS_FLASH_LOC_NONE) {
/*
* set if not already XXX can delete after debug
*/
nffs_inode_setflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
return 1;
}
if (inode_entry == nffs_root_dir) {
return 0;
} else {
return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DUMMY);
}
}
/*
* Return true if inode is marked as deleted.
*/
int
nffs_inode_is_deleted(struct nffs_inode_entry *inode_entry)
{
assert(inode_entry);
return nffs_inode_getflags(inode_entry, NFFS_INODE_FLAG_DELETED);
}
int
nffs_inode_setflags(struct nffs_inode_entry *entry, uint8_t flag)
{
/*
* We shouldn't be setting flags to already deleted inodes
*/
entry->nie_flags |= flag;
return (int)entry->nie_flags;
}
int
nffs_inode_unsetflags(struct nffs_inode_entry *entry, uint8_t flag)
{
entry->nie_flags &= ~flag;
return (int)entry->nie_flags;
}
int
nffs_inode_getflags(struct nffs_inode_entry *entry, uint8_t flag)
{
return (int)(entry->nie_flags & flag);
}