/**
 * Performs a single write operation.  The data written must be no greater
 * than the maximum block data length.  If old data gets overwritten, then
 * the existing data blocks are superseded as necessary.
 *
 * @param write_info            Describes the write operation being perfomred.
 * @param inode_entry           The file inode to write to.
 * @param data                  The new data to write.
 * @param data_len              The number of bytes of new data to write.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_write_chunk(struct nffs_inode_entry *inode_entry, uint32_t file_offset,
                 const void *data, uint16_t data_len)
{
    struct nffs_cache_inode *cache_inode;
    struct nffs_cache_block *cache_block;
    unsigned int gc_count;
    uint32_t append_len;
    uint32_t data_offset;
    uint32_t block_end;
    uint32_t dst_off;
    uint16_t chunk_off;
    uint16_t chunk_sz;
    int rc;

    assert(data_len <= nffs_block_max_data_sz);

    rc = nffs_cache_inode_ensure(&cache_inode, inode_entry);
    if (rc != 0) {
        return rc;
    }

    /** Handle the simple append case first. */
    if (file_offset == cache_inode->nci_file_size) {
        rc = nffs_write_append(cache_inode, data, data_len);
        return rc;
    }

    /** This is not an append; i.e., old data is getting overwritten. */

    dst_off = file_offset + data_len;
    data_offset = data_len;
    cache_block = NULL;

    if (dst_off > cache_inode->nci_file_size) {
        append_len = dst_off - cache_inode->nci_file_size;
    } else {
        append_len = 0;
    }

    do {
        if (cache_block == NULL) {
            rc = nffs_cache_seek(cache_inode, dst_off - 1, &cache_block);
            if (rc != 0) {
                return rc;
            }
        }

        if (cache_block->ncb_file_offset < file_offset) {
            chunk_off = file_offset - cache_block->ncb_file_offset;
        } else {
            chunk_off = 0;
        }

        chunk_sz = cache_block->ncb_block.nb_data_len - chunk_off;
        block_end = cache_block->ncb_file_offset +
                    cache_block->ncb_block.nb_data_len;
        if (block_end != dst_off) {
            chunk_sz += (int)(dst_off - block_end);
        }

        /* Remember the current garbage collection count.  If the count
         * increases during the write, then the block cache has been
         * invalidated and we need to reset our pointers.
         */
        gc_count = nffs_gc_count;

        data_offset = cache_block->ncb_file_offset + chunk_off - file_offset;
        rc = nffs_write_over_block(cache_block->ncb_block.nb_hash_entry,
                                   chunk_off, data + data_offset, chunk_sz);
        if (rc != 0) {
            return rc;
        }

        dst_off -= chunk_sz;

        if (gc_count != nffs_gc_count) {
            /* Garbage collection occurred; the current cached block pointer is
             * invalid, so reset it.  The cached inode is still valid.
             */
            cache_block = NULL;
        } else {
            cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list,
                                     ncb_link);
        }
    } while (data_offset > 0);

    cache_inode->nci_file_size += append_len;
    return 0;
}
/**
 * Performs a single write operation.  The data written must be no greater
 * than the maximum block data length.  If old data gets overwritten, then
 * the existing data blocks are superseded as necessary.
 *
 * @param write_info            Describes the write operation being perfomred.
 * @param inode_entry           The file inode to write to.
 * @param data                  The new data to write.
 * @param data_len              The number of bytes of new data to write.
 *
 * @return                      0 on success; nonzero on failure.
 */
static int
nffs_write_chunk(struct nffs_cache_inode *cache_inode, uint32_t file_offset,
                const void *data, uint16_t data_len)
{
    struct nffs_cache_block *cache_block;
    uint32_t append_len;
    uint32_t data_offset;
    uint32_t block_end;
    uint32_t dst_off;
    uint16_t chunk_off;
    uint16_t chunk_sz;
    int rc;

    assert(data_len <= nffs_block_max_data_sz);

    /** Handle the simple append case first. */
    if (file_offset == cache_inode->nci_file_size) {
        rc = nffs_write_append(cache_inode, data, data_len);
        return rc;
    }

    /** This is not an append; i.e., old data is getting overwritten. */

    dst_off = file_offset + data_len;
    data_offset = data_len;
    cache_block = NULL;

    if (dst_off > cache_inode->nci_file_size) {
        append_len = dst_off - cache_inode->nci_file_size;
    } else {
        append_len = 0;
    }

    do {
        if (cache_block == NULL) {
            rc = nffs_cache_seek(cache_inode, dst_off - 1, &cache_block);
            if (rc != 0) {
                return rc;
            }
        }

        if (cache_block->ncb_file_offset < file_offset) {
            chunk_off = file_offset - cache_block->ncb_file_offset;
        } else {
            chunk_off = 0;
        }

        chunk_sz = cache_block->ncb_block.nb_data_len - chunk_off;
        block_end = cache_block->ncb_file_offset +
                    cache_block->ncb_block.nb_data_len;
        if (block_end != dst_off) {
            chunk_sz += (int)(dst_off - block_end);
        }

        data_offset = cache_block->ncb_file_offset + chunk_off - file_offset;
        rc = nffs_write_over_block(cache_block->ncb_block.nb_hash_entry,
                                   chunk_off, data + data_offset, chunk_sz);
        if (rc != 0) {
            return rc;
        }

        dst_off -= chunk_sz;
        cache_block = TAILQ_PREV(cache_block, nffs_cache_block_list, ncb_link);
    } while (data_offset > 0);

    cache_inode->nci_file_size += append_len;
    return 0;
}