123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752 |
- // 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);
- }
- static 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;
- }
- 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 int append_zeros_no_fallocate(struct backing_file_context *bfc,
- size_t file_size, size_t len)
- {
- u8 buffer[256] = {};
- size_t i;
- for (i = 0; i < len; i += sizeof(buffer)) {
- int to_write = len - i > sizeof(buffer)
- ? sizeof(buffer) : len - i;
- int err = write_to_bf(bfc, buffer, to_write, file_size + i);
- if (err)
- return err;
- }
- return 0;
- }
- /* 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;
- int result;
- 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;
- result = vfs_fallocate(bfc->bc_file, 0, new_last_byte_offset, 1);
- if (result != -EOPNOTSUPP)
- return result;
- return append_zeros_no_fallocate(bfc, file_size, len);
- }
- /*
- * 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_next_md_offset = 0;
- /* 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;
- }
- /*
- * 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;
- }
- int incfs_write_signature_to_backing_file(struct backing_file_context *bfc,
- struct mem_range sig, u32 tree_size,
- loff_t *tree_offset, loff_t *sig_offset)
- {
- 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) {
- sg.sg_sig_size = cpu_to_le32(sig.len);
- sg.sg_sig_offset = cpu_to_le64(rollback_pos);
- result = write_to_bf(bfc, sig.data, sig.len, rollback_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);
- else {
- if (tree_offset)
- *tree_offset = tree_area_pos;
- if (sig_offset)
- *sig_offset = rollback_pos;
- }
- return result;
- }
- static int write_new_status_to_backing_file(struct backing_file_context *bfc,
- u32 data_blocks_written,
- u32 hash_blocks_written)
- {
- int result;
- loff_t rollback_pos;
- struct incfs_status is = {
- .is_header = {
- .h_md_entry_type = INCFS_MD_STATUS,
- .h_record_size = cpu_to_le16(sizeof(is)),
- },
- .is_data_blocks_written = cpu_to_le32(data_blocks_written),
- .is_hash_blocks_written = cpu_to_le32(hash_blocks_written),
- };
- LOCK_REQUIRED(bfc->bc_mutex);
- rollback_pos = incfs_get_end_offset(bfc->bc_file);
- result = append_md_to_backing_file(bfc, &is.is_header);
- if (result)
- truncate_backing_file(bfc, rollback_pos);
- return result;
- }
- int incfs_write_status_to_backing_file(struct backing_file_context *bfc,
- loff_t status_offset,
- u32 data_blocks_written,
- u32 hash_blocks_written)
- {
- struct incfs_status is;
- int result;
- if (!bfc)
- return -EFAULT;
- if (status_offset == 0)
- return write_new_status_to_backing_file(bfc,
- data_blocks_written, hash_blocks_written);
- result = incfs_kread(bfc, &is, sizeof(is), status_offset);
- if (result != sizeof(is))
- return -EIO;
- is.is_data_blocks_written = cpu_to_le32(data_blocks_written);
- is.is_hash_blocks_written = cpu_to_le32(hash_blocks_written);
- result = incfs_kwrite(bfc, &is, sizeof(is), status_offset);
- if (result != sizeof(is))
- return -EIO;
- return 0;
- }
- int incfs_write_verity_signature_to_backing_file(
- struct backing_file_context *bfc, struct mem_range signature,
- loff_t *offset)
- {
- struct incfs_file_verity_signature vs = {};
- int result;
- loff_t pos;
- /* No verity signature section is equivalent to an empty section */
- if (signature.data == NULL || signature.len == 0)
- return 0;
- pos = incfs_get_end_offset(bfc->bc_file);
- vs = (struct incfs_file_verity_signature) {
- .vs_header = (struct incfs_md_header) {
- .h_md_entry_type = INCFS_MD_VERITY_SIGNATURE,
- .h_record_size = cpu_to_le16(sizeof(vs)),
- .h_next_md_offset = cpu_to_le64(0),
- },
- .vs_size = cpu_to_le32(signature.len),
- .vs_offset = cpu_to_le64(pos),
- };
- result = write_to_bf(bfc, signature.data, signature.len, pos);
- if (result)
- goto err;
- result = append_md_to_backing_file(bfc, &vs.vs_header);
- if (result)
- goto err;
- *offset = pos;
- err:
- if (result)
- /* Error, rollback file changes */
- truncate_backing_file(bfc, pos);
- return result;
- }
- /*
- * Write a backing file header
- * It should always be called only on empty file.
- * fh.fh_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 backing file header for a mapping file
- * It should always be called only on empty file.
- */
- int incfs_write_mapping_fh_to_backing_file(struct backing_file_context *bfc,
- incfs_uuid_t *uuid, u64 file_size, u64 offset)
- {
- 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_original_offset = cpu_to_le64(offset);
- fh.fh_data_block_size = cpu_to_le16(INCFS_DATA_FILE_BLOCK_SIZE);
- fh.fh_mapped_file_size = cpu_to_le64(file_size);
- fh.fh_original_uuid = *uuid;
- fh.fh_flags = cpu_to_le32(INCFS_FILE_MAPPED);
- 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);
- return write_to_bf(bfc, &bm_entry, sizeof(bm_entry), bm_entry_off);
- }
- 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_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;
- int res = 0;
- struct incfs_md_header *md_hdr = NULL;
- if (!bfc || !handler)
- return -EFAULT;
- 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);
- 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: %zu",
- 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;
- }
- 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:
- /*
- * File attrs no longer supported, ignore section for
- * compatibility
- */
- break;
- case INCFS_MD_SIGNATURE:
- if (handler->handle_signature)
- res = handler->handle_signature(
- &handler->md_buffer.signature, handler);
- break;
- case INCFS_MD_STATUS:
- if (handler->handle_status)
- res = handler->handle_status(
- &handler->md_buffer.status, handler);
- break;
- case INCFS_MD_VERITY_SIGNATURE:
- if (handler->handle_verity_signature)
- res = handler->handle_verity_signature(
- &handler->md_buffer.verity_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;
- }
|