You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
701 lines
18 KiB
701 lines
18 KiB
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright 2018 Google LLC
|
|
*/
|
|
#include <linux/fs.h>
|
|
#include <linux/file.h>
|
|
#include <linux/types.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/mm.h>
|
|
#include <linux/falloc.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/kernel.h>
|
|
|
|
#include "format.h"
|
|
#include "data_mgmt.h"
|
|
|
|
struct backing_file_context *incfs_alloc_bfc(struct mount_info *mi,
|
|
struct file *backing_file)
|
|
{
|
|
struct backing_file_context *result = NULL;
|
|
|
|
result = kzalloc(sizeof(*result), GFP_NOFS);
|
|
if (!result)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
result->bc_file = get_file(backing_file);
|
|
result->bc_cred = mi->mi_owner;
|
|
mutex_init(&result->bc_mutex);
|
|
return result;
|
|
}
|
|
|
|
void incfs_free_bfc(struct backing_file_context *bfc)
|
|
{
|
|
if (!bfc)
|
|
return;
|
|
|
|
if (bfc->bc_file)
|
|
fput(bfc->bc_file);
|
|
|
|
mutex_destroy(&bfc->bc_mutex);
|
|
kfree(bfc);
|
|
}
|
|
|
|
loff_t incfs_get_end_offset(struct file *f)
|
|
{
|
|
/*
|
|
* This function assumes that file size and the end-offset
|
|
* are the same. This is not always true.
|
|
*/
|
|
return i_size_read(file_inode(f));
|
|
}
|
|
|
|
/*
|
|
* Truncate the tail of the file to the given length.
|
|
* Used to rollback partially successful multistep writes.
|
|
*/
|
|
static int truncate_backing_file(struct backing_file_context *bfc,
|
|
loff_t new_end)
|
|
{
|
|
struct inode *inode = NULL;
|
|
struct dentry *dentry = NULL;
|
|
loff_t old_end = 0;
|
|
struct iattr attr;
|
|
int result = 0;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
if (!bfc->bc_file)
|
|
return -EFAULT;
|
|
|
|
old_end = incfs_get_end_offset(bfc->bc_file);
|
|
if (old_end == new_end)
|
|
return 0;
|
|
if (old_end < new_end)
|
|
return -EINVAL;
|
|
|
|
inode = bfc->bc_file->f_inode;
|
|
dentry = bfc->bc_file->f_path.dentry;
|
|
|
|
attr.ia_size = new_end;
|
|
attr.ia_valid = ATTR_SIZE;
|
|
|
|
inode_lock(inode);
|
|
result = notify_change(dentry, &attr, NULL);
|
|
inode_unlock(inode);
|
|
|
|
return result;
|
|
}
|
|
|
|
/* Append a given number of zero bytes to the end of the backing file. */
|
|
static int append_zeros(struct backing_file_context *bfc, size_t len)
|
|
{
|
|
loff_t file_size = 0;
|
|
loff_t new_last_byte_offset = 0;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
/*
|
|
* Allocate only one byte at the new desired end of the file.
|
|
* It will increase file size and create a zeroed area of
|
|
* a given size.
|
|
*/
|
|
file_size = incfs_get_end_offset(bfc->bc_file);
|
|
new_last_byte_offset = file_size + len - 1;
|
|
return vfs_fallocate(bfc->bc_file, 0, new_last_byte_offset, 1);
|
|
}
|
|
|
|
static int write_to_bf(struct backing_file_context *bfc, const void *buf,
|
|
size_t count, loff_t pos)
|
|
{
|
|
ssize_t res = incfs_kwrite(bfc, buf, count, pos);
|
|
|
|
if (res < 0)
|
|
return res;
|
|
if (res != count)
|
|
return -EIO;
|
|
return 0;
|
|
}
|
|
|
|
static u32 calc_md_crc(struct incfs_md_header *record)
|
|
{
|
|
u32 result = 0;
|
|
__le32 saved_crc = record->h_record_crc;
|
|
__le64 saved_md_offset = record->h_next_md_offset;
|
|
size_t record_size = min_t(size_t, le16_to_cpu(record->h_record_size),
|
|
INCFS_MAX_METADATA_RECORD_SIZE);
|
|
|
|
/* Zero fields which needs to be excluded from CRC calculation. */
|
|
record->h_record_crc = 0;
|
|
record->h_next_md_offset = 0;
|
|
result = crc32(0, record, record_size);
|
|
|
|
/* Restore excluded fields. */
|
|
record->h_record_crc = saved_crc;
|
|
record->h_next_md_offset = saved_md_offset;
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Append a given metadata record to the backing file and update a previous
|
|
* record to add the new record the the metadata list.
|
|
*/
|
|
static int append_md_to_backing_file(struct backing_file_context *bfc,
|
|
struct incfs_md_header *record)
|
|
{
|
|
int result = 0;
|
|
loff_t record_offset;
|
|
loff_t file_pos;
|
|
__le64 new_md_offset;
|
|
size_t record_size;
|
|
|
|
if (!bfc || !record)
|
|
return -EFAULT;
|
|
|
|
if (bfc->bc_last_md_record_offset < 0)
|
|
return -EINVAL;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
record_size = le16_to_cpu(record->h_record_size);
|
|
file_pos = incfs_get_end_offset(bfc->bc_file);
|
|
record->h_prev_md_offset = cpu_to_le64(bfc->bc_last_md_record_offset);
|
|
record->h_next_md_offset = 0;
|
|
record->h_record_crc = cpu_to_le32(calc_md_crc(record));
|
|
|
|
/* Write the metadata record to the end of the backing file */
|
|
record_offset = file_pos;
|
|
new_md_offset = cpu_to_le64(record_offset);
|
|
result = write_to_bf(bfc, record, record_size, file_pos);
|
|
if (result)
|
|
return result;
|
|
|
|
/* Update next metadata offset in a previous record or a superblock. */
|
|
if (bfc->bc_last_md_record_offset) {
|
|
/*
|
|
* Find a place in the previous md record where new record's
|
|
* offset needs to be saved.
|
|
*/
|
|
file_pos = bfc->bc_last_md_record_offset +
|
|
offsetof(struct incfs_md_header, h_next_md_offset);
|
|
} else {
|
|
/*
|
|
* No metadata yet, file a place to update in the
|
|
* file_header.
|
|
*/
|
|
file_pos = offsetof(struct incfs_file_header,
|
|
fh_first_md_offset);
|
|
}
|
|
result = write_to_bf(bfc, &new_md_offset, sizeof(new_md_offset),
|
|
file_pos);
|
|
if (result)
|
|
return result;
|
|
|
|
bfc->bc_last_md_record_offset = record_offset;
|
|
return result;
|
|
}
|
|
|
|
int incfs_write_file_header_flags(struct backing_file_context *bfc, u32 flags)
|
|
{
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
return write_to_bf(bfc, &flags, sizeof(flags),
|
|
offsetof(struct incfs_file_header,
|
|
fh_file_header_flags));
|
|
}
|
|
|
|
/*
|
|
* Reserve 0-filled space for the blockmap body, and append
|
|
* incfs_blockmap metadata record pointing to it.
|
|
*/
|
|
int incfs_write_blockmap_to_backing_file(struct backing_file_context *bfc,
|
|
u32 block_count)
|
|
{
|
|
struct incfs_blockmap blockmap = {};
|
|
int result = 0;
|
|
loff_t file_end = 0;
|
|
size_t map_size = block_count * sizeof(struct incfs_blockmap_entry);
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
blockmap.m_header.h_md_entry_type = INCFS_MD_BLOCK_MAP;
|
|
blockmap.m_header.h_record_size = cpu_to_le16(sizeof(blockmap));
|
|
blockmap.m_header.h_next_md_offset = cpu_to_le64(0);
|
|
blockmap.m_block_count = cpu_to_le32(block_count);
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
/* Reserve 0-filled space for the blockmap body in the backing file. */
|
|
file_end = incfs_get_end_offset(bfc->bc_file);
|
|
result = append_zeros(bfc, map_size);
|
|
if (result)
|
|
return result;
|
|
|
|
/* Write blockmap metadata record pointing to the body written above. */
|
|
blockmap.m_base_offset = cpu_to_le64(file_end);
|
|
result = append_md_to_backing_file(bfc, &blockmap.m_header);
|
|
if (result)
|
|
/* Error, rollback file changes */
|
|
truncate_backing_file(bfc, file_end);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Write file attribute data and metadata record to the backing file.
|
|
*/
|
|
int incfs_write_file_attr_to_backing_file(struct backing_file_context *bfc,
|
|
struct mem_range value, struct incfs_file_attr *attr)
|
|
{
|
|
struct incfs_file_attr file_attr = {};
|
|
int result = 0;
|
|
u32 crc = 0;
|
|
loff_t value_offset = 0;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
if (value.len > INCFS_MAX_FILE_ATTR_SIZE)
|
|
return -ENOSPC;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
crc = crc32(0, value.data, value.len);
|
|
value_offset = incfs_get_end_offset(bfc->bc_file);
|
|
file_attr.fa_header.h_md_entry_type = INCFS_MD_FILE_ATTR;
|
|
file_attr.fa_header.h_record_size = cpu_to_le16(sizeof(file_attr));
|
|
file_attr.fa_header.h_next_md_offset = cpu_to_le64(0);
|
|
file_attr.fa_size = cpu_to_le16((u16)value.len);
|
|
file_attr.fa_offset = cpu_to_le64(value_offset);
|
|
file_attr.fa_crc = cpu_to_le32(crc);
|
|
|
|
result = write_to_bf(bfc, value.data, value.len, value_offset);
|
|
if (result)
|
|
return result;
|
|
|
|
result = append_md_to_backing_file(bfc, &file_attr.fa_header);
|
|
if (result) {
|
|
/* Error, rollback file changes */
|
|
truncate_backing_file(bfc, value_offset);
|
|
} else if (attr) {
|
|
*attr = file_attr;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
|
|
struct mem_range sig, u32 tree_size)
|
|
{
|
|
struct incfs_file_signature sg = {};
|
|
int result = 0;
|
|
loff_t rollback_pos = 0;
|
|
loff_t tree_area_pos = 0;
|
|
size_t alignment = 0;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
rollback_pos = incfs_get_end_offset(bfc->bc_file);
|
|
|
|
sg.sg_header.h_md_entry_type = INCFS_MD_SIGNATURE;
|
|
sg.sg_header.h_record_size = cpu_to_le16(sizeof(sg));
|
|
sg.sg_header.h_next_md_offset = cpu_to_le64(0);
|
|
if (sig.data != NULL && sig.len > 0) {
|
|
loff_t pos = incfs_get_end_offset(bfc->bc_file);
|
|
|
|
sg.sg_sig_size = cpu_to_le32(sig.len);
|
|
sg.sg_sig_offset = cpu_to_le64(pos);
|
|
|
|
result = write_to_bf(bfc, sig.data, sig.len, pos);
|
|
if (result)
|
|
goto err;
|
|
}
|
|
|
|
tree_area_pos = incfs_get_end_offset(bfc->bc_file);
|
|
if (tree_size > 0) {
|
|
if (tree_size > 5 * INCFS_DATA_FILE_BLOCK_SIZE) {
|
|
/*
|
|
* If hash tree is big enough, it makes sense to
|
|
* align in the backing file for faster access.
|
|
*/
|
|
loff_t offset = round_up(tree_area_pos, PAGE_SIZE);
|
|
|
|
alignment = offset - tree_area_pos;
|
|
tree_area_pos = offset;
|
|
}
|
|
|
|
/*
|
|
* If root hash is not the only hash in the tree.
|
|
* reserve 0-filled space for the tree.
|
|
*/
|
|
result = append_zeros(bfc, tree_size + alignment);
|
|
if (result)
|
|
goto err;
|
|
|
|
sg.sg_hash_tree_size = cpu_to_le32(tree_size);
|
|
sg.sg_hash_tree_offset = cpu_to_le64(tree_area_pos);
|
|
}
|
|
|
|
/* Write a hash tree metadata record pointing to the hash tree above. */
|
|
result = append_md_to_backing_file(bfc, &sg.sg_header);
|
|
err:
|
|
if (result)
|
|
/* Error, rollback file changes */
|
|
truncate_backing_file(bfc, rollback_pos);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Write a backing file header
|
|
* It should always be called only on empty file.
|
|
* incfs_super_block.s_first_md_offset is 0 for now, but will be updated
|
|
* once first metadata record is added.
|
|
*/
|
|
int incfs_write_fh_to_backing_file(struct backing_file_context *bfc,
|
|
incfs_uuid_t *uuid, u64 file_size)
|
|
{
|
|
struct incfs_file_header fh = {};
|
|
loff_t file_pos = 0;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
fh.fh_magic = cpu_to_le64(INCFS_MAGIC_NUMBER);
|
|
fh.fh_version = cpu_to_le64(INCFS_FORMAT_CURRENT_VER);
|
|
fh.fh_header_size = cpu_to_le16(sizeof(fh));
|
|
fh.fh_first_md_offset = cpu_to_le64(0);
|
|
fh.fh_data_block_size = cpu_to_le16(INCFS_DATA_FILE_BLOCK_SIZE);
|
|
|
|
fh.fh_file_size = cpu_to_le64(file_size);
|
|
fh.fh_uuid = *uuid;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
file_pos = incfs_get_end_offset(bfc->bc_file);
|
|
if (file_pos != 0)
|
|
return -EEXIST;
|
|
|
|
return write_to_bf(bfc, &fh, sizeof(fh), file_pos);
|
|
}
|
|
|
|
/* Write a given data block and update file's blockmap to point it. */
|
|
int incfs_write_data_block_to_backing_file(struct backing_file_context *bfc,
|
|
struct mem_range block, int block_index,
|
|
loff_t bm_base_off, u16 flags)
|
|
{
|
|
struct incfs_blockmap_entry bm_entry = {};
|
|
int result = 0;
|
|
loff_t data_offset = 0;
|
|
loff_t bm_entry_off =
|
|
bm_base_off + sizeof(struct incfs_blockmap_entry) * block_index;
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
if (block.len >= (1 << 16) || block_index < 0)
|
|
return -EINVAL;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
data_offset = incfs_get_end_offset(bfc->bc_file);
|
|
if (data_offset <= bm_entry_off) {
|
|
/* Blockmap entry is beyond the file's end. It is not normal. */
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Write the block data at the end of the backing file. */
|
|
result = write_to_bf(bfc, block.data, block.len, data_offset);
|
|
if (result)
|
|
return result;
|
|
|
|
/* Update the blockmap to point to the newly written data. */
|
|
bm_entry.me_data_offset_lo = cpu_to_le32((u32)data_offset);
|
|
bm_entry.me_data_offset_hi = cpu_to_le16((u16)(data_offset >> 32));
|
|
bm_entry.me_data_size = cpu_to_le16((u16)block.len);
|
|
bm_entry.me_flags = cpu_to_le16(flags);
|
|
|
|
return write_to_bf(bfc, &bm_entry, sizeof(bm_entry),
|
|
bm_entry_off);
|
|
}
|
|
|
|
int incfs_write_hash_block_to_backing_file(struct backing_file_context *bfc,
|
|
struct mem_range block,
|
|
int block_index,
|
|
loff_t hash_area_off,
|
|
loff_t bm_base_off,
|
|
loff_t file_size)
|
|
{
|
|
struct incfs_blockmap_entry bm_entry = {};
|
|
int result;
|
|
loff_t data_offset = 0;
|
|
loff_t file_end = 0;
|
|
loff_t bm_entry_off =
|
|
bm_base_off +
|
|
sizeof(struct incfs_blockmap_entry) *
|
|
(block_index + get_blocks_count_for_size(file_size));
|
|
|
|
if (!bfc)
|
|
return -EFAULT;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
data_offset = hash_area_off + block_index * INCFS_DATA_FILE_BLOCK_SIZE;
|
|
file_end = incfs_get_end_offset(bfc->bc_file);
|
|
if (data_offset + block.len > file_end) {
|
|
/* Block is located beyond the file's end. It is not normal. */
|
|
return -EINVAL;
|
|
}
|
|
|
|
result = write_to_bf(bfc, block.data, block.len, data_offset);
|
|
if (result)
|
|
return result;
|
|
|
|
bm_entry.me_data_offset_lo = cpu_to_le32((u32)data_offset);
|
|
bm_entry.me_data_offset_hi = cpu_to_le16((u16)(data_offset >> 32));
|
|
bm_entry.me_data_size = cpu_to_le16(INCFS_DATA_FILE_BLOCK_SIZE);
|
|
bm_entry.me_flags = cpu_to_le16(INCFS_BLOCK_HASH);
|
|
|
|
return write_to_bf(bfc, &bm_entry, sizeof(bm_entry), bm_entry_off);
|
|
}
|
|
|
|
/* Initialize a new image in a given backing file. */
|
|
int incfs_make_empty_backing_file(struct backing_file_context *bfc,
|
|
incfs_uuid_t *uuid, u64 file_size)
|
|
{
|
|
int result = 0;
|
|
|
|
if (!bfc || !bfc->bc_file)
|
|
return -EFAULT;
|
|
|
|
result = mutex_lock_interruptible(&bfc->bc_mutex);
|
|
if (result)
|
|
goto out;
|
|
|
|
result = truncate_backing_file(bfc, 0);
|
|
if (result)
|
|
goto out;
|
|
|
|
result = incfs_write_fh_to_backing_file(bfc, uuid, file_size);
|
|
out:
|
|
mutex_unlock(&bfc->bc_mutex);
|
|
return result;
|
|
}
|
|
|
|
int incfs_read_blockmap_entry(struct backing_file_context *bfc, int block_index,
|
|
loff_t bm_base_off,
|
|
struct incfs_blockmap_entry *bm_entry)
|
|
{
|
|
int error = incfs_read_blockmap_entries(bfc, bm_entry, block_index, 1,
|
|
bm_base_off);
|
|
|
|
if (error < 0)
|
|
return error;
|
|
|
|
if (error == 0)
|
|
return -EIO;
|
|
|
|
if (error != 1)
|
|
return -EFAULT;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int incfs_read_blockmap_entries(struct backing_file_context *bfc,
|
|
struct incfs_blockmap_entry *entries,
|
|
int start_index, int blocks_number,
|
|
loff_t bm_base_off)
|
|
{
|
|
loff_t bm_entry_off =
|
|
bm_base_off + sizeof(struct incfs_blockmap_entry) * start_index;
|
|
const size_t bytes_to_read = sizeof(struct incfs_blockmap_entry)
|
|
* blocks_number;
|
|
int result = 0;
|
|
|
|
if (!bfc || !entries)
|
|
return -EFAULT;
|
|
|
|
if (start_index < 0 || bm_base_off <= 0)
|
|
return -ENODATA;
|
|
|
|
result = incfs_kread(bfc, entries, bytes_to_read, bm_entry_off);
|
|
if (result < 0)
|
|
return result;
|
|
return result / sizeof(*entries);
|
|
}
|
|
|
|
int incfs_read_file_header(struct backing_file_context *bfc,
|
|
loff_t *first_md_off, incfs_uuid_t *uuid,
|
|
u64 *file_size, u32 *flags)
|
|
{
|
|
ssize_t bytes_read = 0;
|
|
struct incfs_file_header fh = {};
|
|
|
|
if (!bfc || !first_md_off)
|
|
return -EFAULT;
|
|
|
|
bytes_read = incfs_kread(bfc, &fh, sizeof(fh), 0);
|
|
if (bytes_read < 0)
|
|
return bytes_read;
|
|
|
|
if (bytes_read < sizeof(fh))
|
|
return -EBADMSG;
|
|
|
|
if (le64_to_cpu(fh.fh_magic) != INCFS_MAGIC_NUMBER)
|
|
return -EILSEQ;
|
|
|
|
if (le64_to_cpu(fh.fh_version) > INCFS_FORMAT_CURRENT_VER)
|
|
return -EILSEQ;
|
|
|
|
if (le16_to_cpu(fh.fh_data_block_size) != INCFS_DATA_FILE_BLOCK_SIZE)
|
|
return -EILSEQ;
|
|
|
|
if (le16_to_cpu(fh.fh_header_size) != sizeof(fh))
|
|
return -EILSEQ;
|
|
|
|
if (first_md_off)
|
|
*first_md_off = le64_to_cpu(fh.fh_first_md_offset);
|
|
if (uuid)
|
|
*uuid = fh.fh_uuid;
|
|
if (file_size)
|
|
*file_size = le64_to_cpu(fh.fh_file_size);
|
|
if (flags)
|
|
*flags = le32_to_cpu(fh.fh_file_header_flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Read through metadata records from the backing file one by one
|
|
* and call provided metadata handlers.
|
|
*/
|
|
int incfs_read_next_metadata_record(struct backing_file_context *bfc,
|
|
struct metadata_handler *handler)
|
|
{
|
|
const ssize_t max_md_size = INCFS_MAX_METADATA_RECORD_SIZE;
|
|
ssize_t bytes_read = 0;
|
|
size_t md_record_size = 0;
|
|
loff_t next_record = 0;
|
|
loff_t prev_record = 0;
|
|
int res = 0;
|
|
struct incfs_md_header *md_hdr = NULL;
|
|
|
|
if (!bfc || !handler)
|
|
return -EFAULT;
|
|
|
|
LOCK_REQUIRED(bfc->bc_mutex);
|
|
|
|
if (handler->md_record_offset == 0)
|
|
return -EPERM;
|
|
|
|
memset(&handler->md_buffer, 0, max_md_size);
|
|
bytes_read = incfs_kread(bfc, &handler->md_buffer, max_md_size,
|
|
handler->md_record_offset);
|
|
if (bytes_read < 0)
|
|
return bytes_read;
|
|
if (bytes_read < sizeof(*md_hdr))
|
|
return -EBADMSG;
|
|
|
|
md_hdr = &handler->md_buffer.md_header;
|
|
next_record = le64_to_cpu(md_hdr->h_next_md_offset);
|
|
prev_record = le64_to_cpu(md_hdr->h_prev_md_offset);
|
|
md_record_size = le16_to_cpu(md_hdr->h_record_size);
|
|
|
|
if (md_record_size > max_md_size) {
|
|
pr_warn("incfs: The record is too large. Size: %ld",
|
|
md_record_size);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
if (bytes_read < md_record_size) {
|
|
pr_warn("incfs: The record hasn't been fully read.");
|
|
return -EBADMSG;
|
|
}
|
|
|
|
if (next_record <= handler->md_record_offset && next_record != 0) {
|
|
pr_warn("incfs: Next record (%lld) points back in file.",
|
|
next_record);
|
|
return -EBADMSG;
|
|
}
|
|
|
|
if (prev_record != handler->md_prev_record_offset) {
|
|
pr_warn("incfs: Metadata chain has been corrupted.");
|
|
return -EBADMSG;
|
|
}
|
|
|
|
if (le32_to_cpu(md_hdr->h_record_crc) != calc_md_crc(md_hdr)) {
|
|
pr_warn("incfs: Metadata CRC mismatch.");
|
|
return -EBADMSG;
|
|
}
|
|
|
|
switch (md_hdr->h_md_entry_type) {
|
|
case INCFS_MD_NONE:
|
|
break;
|
|
case INCFS_MD_BLOCK_MAP:
|
|
if (handler->handle_blockmap)
|
|
res = handler->handle_blockmap(
|
|
&handler->md_buffer.blockmap, handler);
|
|
break;
|
|
case INCFS_MD_FILE_ATTR:
|
|
if (handler->handle_file_attr)
|
|
res = handler->handle_file_attr(
|
|
&handler->md_buffer.file_attr, handler);
|
|
break;
|
|
case INCFS_MD_SIGNATURE:
|
|
if (handler->handle_signature)
|
|
res = handler->handle_signature(
|
|
&handler->md_buffer.signature, handler);
|
|
break;
|
|
default:
|
|
res = -ENOTSUPP;
|
|
break;
|
|
}
|
|
|
|
if (!res) {
|
|
if (next_record == 0) {
|
|
/*
|
|
* Zero offset for the next record means that the last
|
|
* metadata record has just been processed.
|
|
*/
|
|
bfc->bc_last_md_record_offset =
|
|
handler->md_record_offset;
|
|
}
|
|
handler->md_prev_record_offset = handler->md_record_offset;
|
|
handler->md_record_offset = next_record;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
ssize_t incfs_kread(struct backing_file_context *bfc, void *buf, size_t size,
|
|
loff_t pos)
|
|
{
|
|
const struct cred *old_cred = override_creds(bfc->bc_cred);
|
|
int ret = kernel_read(bfc->bc_file, buf, size, &pos);
|
|
|
|
revert_creds(old_cred);
|
|
return ret;
|
|
}
|
|
|
|
ssize_t incfs_kwrite(struct backing_file_context *bfc, const void *buf,
|
|
size_t size, loff_t pos)
|
|
{
|
|
const struct cred *old_cred = override_creds(bfc->bc_cred);
|
|
int ret = kernel_write(bfc->bc_file, buf, size, &pos);
|
|
|
|
revert_creds(old_cred);
|
|
return ret;
|
|
}
|
|
|