| // SPDX-License-Identifier: BSD-2-Clause |
| /* |
| * Copyright (c) 2015, Linaro Limited |
| */ |
| |
| #include <assert.h> |
| #include <kernel/mutex.h> |
| #include <kernel/panic.h> |
| #include <kernel/thread.h> |
| #include <mempool.h> |
| #include <mm/core_memprot.h> |
| #include <mm/tee_pager.h> |
| #include <optee_rpc_cmd.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string_ext.h> |
| #include <string.h> |
| #include <sys/queue.h> |
| #include <tee/fs_dirfile.h> |
| #include <tee/fs_htree.h> |
| #include <tee/tee_fs.h> |
| #include <tee/tee_fs_rpc.h> |
| #include <tee/tee_pobj.h> |
| #include <trace.h> |
| #include <utee_defines.h> |
| #include <util.h> |
| |
| #define BLOCK_SHIFT 12 |
| |
| #define BLOCK_SIZE (1 << BLOCK_SHIFT) |
| |
| struct tee_fs_fd { |
| struct tee_fs_htree *ht; |
| int fd; |
| struct tee_fs_dirfile_fileh dfh; |
| const TEE_UUID *uuid; |
| }; |
| |
| struct tee_fs_dir { |
| struct tee_fs_dirfile_dirh *dirh; |
| int idx; |
| struct tee_fs_dirent d; |
| const TEE_UUID *uuid; |
| }; |
| |
| static int pos_to_block_num(int position) |
| { |
| return position >> BLOCK_SHIFT; |
| } |
| |
| static struct mutex ree_fs_mutex = MUTEX_INITIALIZER; |
| |
| static void *get_tmp_block(void) |
| { |
| return mempool_alloc(mempool_default, BLOCK_SIZE); |
| } |
| |
| static void put_tmp_block(void *tmp_block) |
| { |
| mempool_free(mempool_default, tmp_block); |
| } |
| |
| static TEE_Result out_of_place_write(struct tee_fs_fd *fdp, size_t pos, |
| const void *buf, size_t len) |
| { |
| TEE_Result res; |
| size_t start_block_num = pos_to_block_num(pos); |
| size_t end_block_num = pos_to_block_num(pos + len - 1); |
| size_t remain_bytes = len; |
| uint8_t *data_ptr = (uint8_t *)buf; |
| uint8_t *block; |
| struct tee_fs_htree_meta *meta = tee_fs_htree_get_meta(fdp->ht); |
| |
| /* |
| * It doesn't make sense to call this function if nothing is to be |
| * written. This also guards against end_block_num getting an |
| * unexpected value when pos == 0 and len == 0. |
| */ |
| if (!len) |
| return TEE_ERROR_BAD_PARAMETERS; |
| |
| block = get_tmp_block(); |
| if (!block) |
| return TEE_ERROR_OUT_OF_MEMORY; |
| |
| while (start_block_num <= end_block_num) { |
| size_t offset = pos % BLOCK_SIZE; |
| size_t size_to_write = MIN(remain_bytes, (size_t)BLOCK_SIZE); |
| |
| if (size_to_write + offset > BLOCK_SIZE) |
| size_to_write = BLOCK_SIZE - offset; |
| |
| if (start_block_num * BLOCK_SIZE < |
| ROUNDUP(meta->length, BLOCK_SIZE)) { |
| res = tee_fs_htree_read_block(&fdp->ht, |
| start_block_num, block); |
| if (res != TEE_SUCCESS) |
| goto exit; |
| } else { |
| memset(block, 0, BLOCK_SIZE); |
| } |
| |
| if (data_ptr) |
| memcpy(block + offset, data_ptr, size_to_write); |
| else |
| memset(block + offset, 0, size_to_write); |
| |
| res = tee_fs_htree_write_block(&fdp->ht, start_block_num, |
| block); |
| if (res != TEE_SUCCESS) |
| goto exit; |
| |
| if (data_ptr) |
| data_ptr += size_to_write; |
| remain_bytes -= size_to_write; |
| start_block_num++; |
| pos += size_to_write; |
| } |
| |
| if (pos > meta->length) { |
| meta->length = pos; |
| tee_fs_htree_meta_set_dirty(fdp->ht); |
| } |
| |
| exit: |
| if (block) |
| put_tmp_block(block); |
| return res; |
| } |
| |
| static TEE_Result get_offs_size(enum tee_fs_htree_type type, size_t idx, |
| uint8_t vers, size_t *offs, size_t *size) |
| { |
| const size_t node_size = sizeof(struct tee_fs_htree_node_image); |
| const size_t block_nodes = BLOCK_SIZE / (node_size * 2); |
| size_t pbn; |
| size_t bidx; |
| |
| assert(vers == 0 || vers == 1); |
| |
| /* |
| * File layout |
| * [demo with input: |
| * BLOCK_SIZE = 4096, |
| * node_size = 66, |
| * block_nodes = 4096/(66*2) = 31 ] |
| * |
| * phys block 0: |
| * tee_fs_htree_image vers 0 @ offs = 0 |
| * tee_fs_htree_image vers 1 @ offs = sizeof(tee_fs_htree_image) |
| * |
| * phys block 1: |
| * tee_fs_htree_node_image 0 vers 0 @ offs = 0 |
| * tee_fs_htree_node_image 0 vers 1 @ offs = node_size |
| * tee_fs_htree_node_image 1 vers 0 @ offs = node_size * 2 |
| * tee_fs_htree_node_image 1 vers 1 @ offs = node_size * 3 |
| * ... |
| * tee_fs_htree_node_image 30 vers 0 @ offs = node_size * 60 |
| * tee_fs_htree_node_image 30 vers 1 @ offs = node_size * 61 |
| * |
| * phys block 2: |
| * data block 0 vers 0 |
| * |
| * phys block 3: |
| * data block 0 vers 1 |
| * |
| * ... |
| * phys block 62: |
| * data block 30 vers 0 |
| * |
| * phys block 63: |
| * data block 30 vers 1 |
| * |
| * phys block 64: |
| * tee_fs_htree_node_image 31 vers 0 @ offs = 0 |
| * tee_fs_htree_node_image 31 vers 1 @ offs = node_size |
| * tee_fs_htree_node_image 32 vers 0 @ offs = node_size * 2 |
| * tee_fs_htree_node_image 32 vers 1 @ offs = node_size * 3 |
| * ... |
| * tee_fs_htree_node_image 61 vers 0 @ offs = node_size * 60 |
| * tee_fs_htree_node_image 61 vers 1 @ offs = node_size * 61 |
| * |
| * phys block 65: |
| * data block 31 vers 0 |
| * |
| * phys block 66: |
| * data block 31 vers 1 |
| * ... |
| */ |
| |
| switch (type) { |
| case TEE_FS_HTREE_TYPE_HEAD: |
| *offs = sizeof(struct tee_fs_htree_image) * vers; |
| *size = sizeof(struct tee_fs_htree_image); |
| return TEE_SUCCESS; |
| case TEE_FS_HTREE_TYPE_NODE: |
| pbn = 1 + ((idx / block_nodes) * block_nodes * 2); |
| *offs = pbn * BLOCK_SIZE + |
| 2 * node_size * (idx % block_nodes) + |
| node_size * vers; |
| *size = node_size; |
| return TEE_SUCCESS; |
| case TEE_FS_HTREE_TYPE_BLOCK: |
| bidx = 2 * idx + vers; |
| pbn = 2 + bidx + bidx / (block_nodes * 2 - 1); |
| *offs = pbn * BLOCK_SIZE; |
| *size = BLOCK_SIZE; |
| return TEE_SUCCESS; |
| default: |
| return TEE_ERROR_GENERIC; |
| } |
| } |
| |
| static TEE_Result ree_fs_rpc_read_init(void *aux, |
| struct tee_fs_rpc_operation *op, |
| enum tee_fs_htree_type type, size_t idx, |
| uint8_t vers, void **data) |
| { |
| struct tee_fs_fd *fdp = aux; |
| TEE_Result res; |
| size_t offs; |
| size_t size; |
| |
| res = get_offs_size(type, idx, vers, &offs, &size); |
| if (res != TEE_SUCCESS) |
| return res; |
| |
| return tee_fs_rpc_read_init(op, OPTEE_RPC_CMD_FS, fdp->fd, |
| offs, size, data); |
| } |
| |
| static TEE_Result ree_fs_rpc_write_init(void *aux, |
| struct tee_fs_rpc_operation *op, |
| enum tee_fs_htree_type type, size_t idx, |
| uint8_t vers, void **data) |
| { |
| struct tee_fs_fd *fdp = aux; |
| TEE_Result res; |
| size_t offs; |
| size_t size; |
| |
| res = get_offs_size(type, idx, vers, &offs, &size); |
| if (res != TEE_SUCCESS) |
| return res; |
| |
| return tee_fs_rpc_write_init(op, OPTEE_RPC_CMD_FS, fdp->fd, |
| offs, size, data); |
| } |
| |
| static const struct tee_fs_htree_storage ree_fs_storage_ops = { |
| .block_size = BLOCK_SIZE, |
| .rpc_read_init = ree_fs_rpc_read_init, |
| .rpc_read_final = tee_fs_rpc_read_final, |
| .rpc_write_init = ree_fs_rpc_write_init, |
| .rpc_write_final = tee_fs_rpc_write_final, |
| }; |
| |
| static TEE_Result ree_fs_ftruncate_internal(struct tee_fs_fd *fdp, |
| tee_fs_off_t new_file_len) |
| { |
| TEE_Result res; |
| struct tee_fs_htree_meta *meta = tee_fs_htree_get_meta(fdp->ht); |
| |
| if ((size_t)new_file_len > meta->length) { |
| size_t ext_len = new_file_len - meta->length; |
| |
| res = out_of_place_write(fdp, meta->length, NULL, ext_len); |
| if (res != TEE_SUCCESS) |
| return res; |
| } else { |
| size_t offs; |
| size_t sz; |
| |
| res = get_offs_size(TEE_FS_HTREE_TYPE_BLOCK, |
| ROUNDUP(new_file_len, BLOCK_SIZE) / |
| BLOCK_SIZE, 1, &offs, &sz); |
| if (res != TEE_SUCCESS) |
| return res; |
| |
| res = tee_fs_htree_truncate(&fdp->ht, |
| new_file_len / BLOCK_SIZE); |
| if (res != TEE_SUCCESS) |
| return res; |
| |
| res = tee_fs_rpc_truncate(OPTEE_RPC_CMD_FS, fdp->fd, |
| offs + sz); |
| if (res != TEE_SUCCESS) |
| return res; |
| |
| meta->length = new_file_len; |
| tee_fs_htree_meta_set_dirty(fdp->ht); |
| } |
| |
| return TEE_SUCCESS; |
| } |
| |
| static TEE_Result ree_fs_read_primitive(struct tee_file_handle *fh, size_t pos, |
| void *buf, size_t *len) |
| { |
| TEE_Result res; |
| int start_block_num; |
| int end_block_num; |
| size_t remain_bytes; |
| uint8_t *data_ptr = buf; |
| uint8_t *block = NULL; |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| struct tee_fs_htree_meta *meta = tee_fs_htree_get_meta(fdp->ht); |
| |
| remain_bytes = *len; |
| if ((pos + remain_bytes) < remain_bytes || pos > meta->length) |
| remain_bytes = 0; |
| else if (pos + remain_bytes > meta->length) |
| remain_bytes = meta->length - pos; |
| |
| *len = remain_bytes; |
| |
| if (!remain_bytes) { |
| res = TEE_SUCCESS; |
| goto exit; |
| } |
| |
| start_block_num = pos_to_block_num(pos); |
| end_block_num = pos_to_block_num(pos + remain_bytes - 1); |
| |
| block = get_tmp_block(); |
| if (!block) { |
| res = TEE_ERROR_OUT_OF_MEMORY; |
| goto exit; |
| } |
| |
| while (start_block_num <= end_block_num) { |
| size_t offset = pos % BLOCK_SIZE; |
| size_t size_to_read = MIN(remain_bytes, (size_t)BLOCK_SIZE); |
| |
| if (size_to_read + offset > BLOCK_SIZE) |
| size_to_read = BLOCK_SIZE - offset; |
| |
| res = tee_fs_htree_read_block(&fdp->ht, start_block_num, block); |
| if (res != TEE_SUCCESS) |
| goto exit; |
| |
| memcpy(data_ptr, block + offset, size_to_read); |
| |
| data_ptr += size_to_read; |
| remain_bytes -= size_to_read; |
| pos += size_to_read; |
| |
| start_block_num++; |
| } |
| res = TEE_SUCCESS; |
| exit: |
| if (block) |
| put_tmp_block(block); |
| return res; |
| } |
| |
| static TEE_Result ree_fs_read(struct tee_file_handle *fh, size_t pos, |
| void *buf, size_t *len) |
| { |
| TEE_Result res; |
| |
| mutex_lock(&ree_fs_mutex); |
| res = ree_fs_read_primitive(fh, pos, buf, len); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result ree_fs_write_primitive(struct tee_file_handle *fh, size_t pos, |
| const void *buf, size_t len) |
| { |
| TEE_Result res; |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| size_t file_size; |
| |
| if (!len) |
| return TEE_SUCCESS; |
| |
| file_size = tee_fs_htree_get_meta(fdp->ht)->length; |
| |
| if ((pos + len) < len) |
| return TEE_ERROR_BAD_PARAMETERS; |
| |
| if (file_size < pos) { |
| res = ree_fs_ftruncate_internal(fdp, pos); |
| if (res != TEE_SUCCESS) |
| return res; |
| } |
| |
| return out_of_place_write(fdp, pos, buf, len); |
| } |
| |
| static TEE_Result ree_fs_open_primitive(bool create, uint8_t *hash, |
| const TEE_UUID *uuid, |
| struct tee_fs_dirfile_fileh *dfh, |
| struct tee_file_handle **fh) |
| { |
| TEE_Result res; |
| struct tee_fs_fd *fdp; |
| |
| fdp = calloc(1, sizeof(struct tee_fs_fd)); |
| if (!fdp) |
| return TEE_ERROR_OUT_OF_MEMORY; |
| fdp->fd = -1; |
| fdp->uuid = uuid; |
| |
| if (create) |
| res = tee_fs_rpc_create_dfh(OPTEE_RPC_CMD_FS, |
| dfh, &fdp->fd); |
| else |
| res = tee_fs_rpc_open_dfh(OPTEE_RPC_CMD_FS, dfh, &fdp->fd); |
| |
| if (res != TEE_SUCCESS) |
| goto out; |
| |
| res = tee_fs_htree_open(create, hash, uuid, &ree_fs_storage_ops, |
| fdp, &fdp->ht); |
| out: |
| if (res == TEE_SUCCESS) { |
| if (dfh) |
| fdp->dfh = *dfh; |
| else |
| fdp->dfh.idx = -1; |
| *fh = (struct tee_file_handle *)fdp; |
| } else { |
| if (fdp->fd != -1) |
| tee_fs_rpc_close(OPTEE_RPC_CMD_FS, fdp->fd); |
| if (create) |
| tee_fs_rpc_remove_dfh(OPTEE_RPC_CMD_FS, dfh); |
| free(fdp); |
| } |
| |
| return res; |
| } |
| |
| static void ree_fs_close_primitive(struct tee_file_handle *fh) |
| { |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| |
| if (fdp) { |
| tee_fs_htree_close(&fdp->ht); |
| tee_fs_rpc_close(OPTEE_RPC_CMD_FS, fdp->fd); |
| free(fdp); |
| } |
| } |
| |
| static TEE_Result ree_dirf_commit_writes(struct tee_file_handle *fh, |
| uint8_t *hash) |
| { |
| TEE_Result res; |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| |
| res = tee_fs_htree_sync_to_storage(&fdp->ht, fdp->dfh.hash); |
| |
| if (!res && hash) |
| memcpy(hash, fdp->dfh.hash, sizeof(fdp->dfh.hash)); |
| |
| return res; |
| } |
| |
| static const struct tee_fs_dirfile_operations ree_dirf_ops = { |
| .open = ree_fs_open_primitive, |
| .close = ree_fs_close_primitive, |
| .read = ree_fs_read_primitive, |
| .write = ree_fs_write_primitive, |
| .commit_writes = ree_dirf_commit_writes, |
| }; |
| |
| static struct tee_fs_dirfile_dirh *ree_fs_dirh; |
| static size_t ree_fs_dirh_refcount; |
| |
| #ifdef CFG_RPMB_FS |
| static struct tee_file_handle *ree_fs_rpmb_fh; |
| |
| static TEE_Result open_dirh(struct tee_fs_dirfile_dirh **dirh) |
| { |
| TEE_Result res; |
| uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; |
| uint8_t *hashp = NULL; |
| const char fname[] = "dirfile.db.hash"; |
| |
| res = tee_rpmb_fs_raw_open(fname, false, &ree_fs_rpmb_fh); |
| if (!res) { |
| size_t l = sizeof(hash); |
| |
| res = rpmb_fs_ops.read(ree_fs_rpmb_fh, 0, hash, &l); |
| if (res) |
| return res; |
| if (l == sizeof(hash)) |
| hashp = hash; |
| } else if (res == TEE_ERROR_ITEM_NOT_FOUND) { |
| res = tee_rpmb_fs_raw_open(fname, true, &ree_fs_rpmb_fh); |
| } |
| if (res) |
| return res; |
| |
| res = tee_fs_dirfile_open(false, hashp, &ree_dirf_ops, dirh); |
| if (res == TEE_ERROR_ITEM_NOT_FOUND) |
| res = tee_fs_dirfile_open(true, NULL, &ree_dirf_ops, dirh); |
| |
| if (res) |
| rpmb_fs_ops.close(&ree_fs_rpmb_fh); |
| |
| return res; |
| } |
| |
| static TEE_Result commit_dirh_writes(struct tee_fs_dirfile_dirh *dirh) |
| { |
| TEE_Result res; |
| uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; |
| |
| res = tee_fs_dirfile_commit_writes(dirh, hash); |
| if (res) |
| return res; |
| return rpmb_fs_ops.write(ree_fs_rpmb_fh, 0, hash, sizeof(hash)); |
| } |
| |
| static void close_dirh(struct tee_fs_dirfile_dirh **dirh) |
| { |
| tee_fs_dirfile_close(*dirh); |
| *dirh = NULL; |
| rpmb_fs_ops.close(&ree_fs_rpmb_fh); |
| } |
| |
| #else /*!CFG_RPMB_FS*/ |
| static TEE_Result open_dirh(struct tee_fs_dirfile_dirh **dirh) |
| { |
| TEE_Result res; |
| |
| res = tee_fs_dirfile_open(false, NULL, &ree_dirf_ops, dirh); |
| if (res == TEE_ERROR_ITEM_NOT_FOUND) |
| return tee_fs_dirfile_open(true, NULL, &ree_dirf_ops, dirh); |
| |
| return res; |
| } |
| |
| static TEE_Result commit_dirh_writes(struct tee_fs_dirfile_dirh *dirh) |
| { |
| return tee_fs_dirfile_commit_writes(dirh, NULL); |
| } |
| |
| static void close_dirh(struct tee_fs_dirfile_dirh **dirh) |
| { |
| tee_fs_dirfile_close(*dirh); |
| *dirh = NULL; |
| } |
| #endif /*!CFG_RPMB_FS*/ |
| |
| static TEE_Result get_dirh(struct tee_fs_dirfile_dirh **dirh) |
| { |
| if (!ree_fs_dirh) { |
| TEE_Result res = open_dirh(&ree_fs_dirh); |
| |
| if (res) { |
| *dirh = NULL; |
| return res; |
| } |
| } |
| ree_fs_dirh_refcount++; |
| assert(ree_fs_dirh); |
| assert(ree_fs_dirh_refcount); |
| *dirh = ree_fs_dirh; |
| return TEE_SUCCESS; |
| } |
| |
| static void put_dirh_primitive(bool close) |
| { |
| assert(ree_fs_dirh_refcount); |
| |
| /* |
| * During the execution of one of the ree_fs_ops ree_fs_dirh is |
| * guareteed to be a valid pointer. But when the fop has returned |
| * another thread may get an error or something causing that fop |
| * to do a put with close=1. |
| * |
| * For all fops but ree_fs_close() there's a call to get_dirh() to |
| * get a new dirh which will open it again if it was closed before. |
| * But in the ree_fs_close() case there's no call to get_dirh() |
| * only to this function, put_dirh_primitive(), and in this case |
| * ree_fs_dirh may actually be NULL. |
| */ |
| ree_fs_dirh_refcount--; |
| if (ree_fs_dirh && (!ree_fs_dirh_refcount || close)) |
| close_dirh(&ree_fs_dirh); |
| } |
| |
| static void put_dirh(struct tee_fs_dirfile_dirh *dirh, bool close) |
| { |
| if (dirh) { |
| assert(dirh == ree_fs_dirh); |
| put_dirh_primitive(close); |
| } |
| } |
| |
| static TEE_Result ree_fs_open(struct tee_pobj *po, size_t *size, |
| struct tee_file_handle **fh) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_dirfile_fileh dfh; |
| |
| mutex_lock(&ree_fs_mutex); |
| |
| res = get_dirh(&dirh); |
| if (res != TEE_SUCCESS) |
| goto out; |
| |
| res = tee_fs_dirfile_find(dirh, &po->uuid, po->obj_id, po->obj_id_len, |
| &dfh); |
| if (res != TEE_SUCCESS) |
| goto out; |
| |
| res = ree_fs_open_primitive(false, dfh.hash, &po->uuid, &dfh, fh); |
| if (res == TEE_ERROR_ITEM_NOT_FOUND) { |
| /* |
| * If the object isn't found someone has tampered with it, |
| * treat it as corrupt. |
| */ |
| res = TEE_ERROR_CORRUPT_OBJECT; |
| } else if (!res && size) { |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)*fh; |
| |
| *size = tee_fs_htree_get_meta(fdp->ht)->length; |
| } |
| |
| out: |
| if (res) |
| put_dirh(dirh, false); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result set_name(struct tee_fs_dirfile_dirh *dirh, |
| struct tee_fs_fd *fdp, struct tee_pobj *po, |
| bool overwrite) |
| { |
| TEE_Result res; |
| bool have_old_dfh = false; |
| struct tee_fs_dirfile_fileh old_dfh = { .idx = -1 }; |
| |
| res = tee_fs_dirfile_find(dirh, &po->uuid, po->obj_id, po->obj_id_len, |
| &old_dfh); |
| if (!overwrite && !res) |
| return TEE_ERROR_ACCESS_CONFLICT; |
| |
| if (!res) |
| have_old_dfh = true; |
| |
| /* |
| * If old_dfh wasn't found, the idx will be -1 and |
| * tee_fs_dirfile_rename() will allocate a new index. |
| */ |
| fdp->dfh.idx = old_dfh.idx; |
| old_dfh.idx = -1; |
| res = tee_fs_dirfile_rename(dirh, &po->uuid, &fdp->dfh, |
| po->obj_id, po->obj_id_len); |
| if (res) |
| return res; |
| |
| res = commit_dirh_writes(dirh); |
| if (res) |
| return res; |
| |
| if (have_old_dfh) |
| tee_fs_rpc_remove_dfh(OPTEE_RPC_CMD_FS, &old_dfh); |
| |
| return TEE_SUCCESS; |
| } |
| |
| static void ree_fs_close(struct tee_file_handle **fh) |
| { |
| if (*fh) { |
| mutex_lock(&ree_fs_mutex); |
| put_dirh_primitive(false); |
| ree_fs_close_primitive(*fh); |
| *fh = NULL; |
| mutex_unlock(&ree_fs_mutex); |
| |
| } |
| } |
| |
| static TEE_Result ree_fs_create(struct tee_pobj *po, bool overwrite, |
| const void *head, size_t head_size, |
| const void *attr, size_t attr_size, |
| const void *data, size_t data_size, |
| struct tee_file_handle **fh) |
| { |
| struct tee_fs_fd *fdp; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_dirfile_fileh dfh; |
| TEE_Result res; |
| size_t pos = 0; |
| |
| *fh = NULL; |
| mutex_lock(&ree_fs_mutex); |
| |
| res = get_dirh(&dirh); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_get_tmp(dirh, &dfh); |
| if (res) |
| goto out; |
| |
| res = ree_fs_open_primitive(true, dfh.hash, &po->uuid, &dfh, fh); |
| if (res) |
| goto out; |
| |
| if (head && head_size) { |
| res = ree_fs_write_primitive(*fh, pos, head, head_size); |
| if (res) |
| goto out; |
| pos += head_size; |
| } |
| |
| if (attr && attr_size) { |
| res = ree_fs_write_primitive(*fh, pos, attr, attr_size); |
| if (res) |
| goto out; |
| pos += attr_size; |
| } |
| |
| if (data && data_size) { |
| res = ree_fs_write_primitive(*fh, pos, data, data_size); |
| if (res) |
| goto out; |
| } |
| |
| fdp = (struct tee_fs_fd *)*fh; |
| res = tee_fs_htree_sync_to_storage(&fdp->ht, fdp->dfh.hash); |
| if (res) |
| goto out; |
| |
| res = set_name(dirh, fdp, po, overwrite); |
| out: |
| if (res) { |
| put_dirh(dirh, true); |
| if (*fh) { |
| ree_fs_close_primitive(*fh); |
| *fh = NULL; |
| tee_fs_rpc_remove_dfh(OPTEE_RPC_CMD_FS, &dfh); |
| } |
| } |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result ree_fs_write(struct tee_file_handle *fh, size_t pos, |
| const void *buf, size_t len) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| |
| mutex_lock(&ree_fs_mutex); |
| |
| res = get_dirh(&dirh); |
| if (res) |
| goto out; |
| |
| res = ree_fs_write_primitive(fh, pos, buf, len); |
| if (res) |
| goto out; |
| |
| res = tee_fs_htree_sync_to_storage(&fdp->ht, fdp->dfh.hash); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_update_hash(dirh, &fdp->dfh); |
| if (res) |
| goto out; |
| res = commit_dirh_writes(dirh); |
| out: |
| put_dirh(dirh, res); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result ree_fs_rename(struct tee_pobj *old, struct tee_pobj *new, |
| bool overwrite) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_dirfile_fileh dfh; |
| struct tee_fs_dirfile_fileh remove_dfh = { .idx = -1 }; |
| |
| if (!new) |
| return TEE_ERROR_BAD_PARAMETERS; |
| |
| mutex_lock(&ree_fs_mutex); |
| res = get_dirh(&dirh); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_find(dirh, &new->uuid, new->obj_id, |
| new->obj_id_len, &remove_dfh); |
| if (!res && !overwrite) { |
| res = TEE_ERROR_ACCESS_CONFLICT; |
| goto out; |
| } |
| |
| res = tee_fs_dirfile_find(dirh, &old->uuid, old->obj_id, |
| old->obj_id_len, &dfh); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_rename(dirh, &new->uuid, &dfh, new->obj_id, |
| new->obj_id_len); |
| if (res) |
| goto out; |
| |
| if (remove_dfh.idx != -1) { |
| res = tee_fs_dirfile_remove(dirh, &remove_dfh); |
| if (res) |
| goto out; |
| } |
| |
| res = commit_dirh_writes(dirh); |
| if (res) |
| goto out; |
| |
| if (remove_dfh.idx != -1) |
| tee_fs_rpc_remove_dfh(OPTEE_RPC_CMD_FS, &remove_dfh); |
| |
| out: |
| put_dirh(dirh, res); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| |
| } |
| |
| static TEE_Result ree_fs_remove(struct tee_pobj *po) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_dirfile_fileh dfh; |
| |
| mutex_lock(&ree_fs_mutex); |
| res = get_dirh(&dirh); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_find(dirh, &po->uuid, po->obj_id, po->obj_id_len, |
| &dfh); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_remove(dirh, &dfh); |
| if (res) |
| goto out; |
| |
| res = commit_dirh_writes(dirh); |
| if (res) |
| goto out; |
| |
| tee_fs_rpc_remove_dfh(OPTEE_RPC_CMD_FS, &dfh); |
| |
| assert(tee_fs_dirfile_find(dirh, &po->uuid, po->obj_id, po->obj_id_len, |
| &dfh)); |
| out: |
| put_dirh(dirh, res); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result ree_fs_truncate(struct tee_file_handle *fh, size_t len) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = NULL; |
| struct tee_fs_fd *fdp = (struct tee_fs_fd *)fh; |
| |
| mutex_lock(&ree_fs_mutex); |
| |
| res = get_dirh(&dirh); |
| if (res) |
| goto out; |
| |
| res = ree_fs_ftruncate_internal(fdp, len); |
| if (res) |
| goto out; |
| |
| res = tee_fs_htree_sync_to_storage(&fdp->ht, fdp->dfh.hash); |
| if (res) |
| goto out; |
| |
| res = tee_fs_dirfile_update_hash(dirh, &fdp->dfh); |
| if (res) |
| goto out; |
| res = commit_dirh_writes(dirh); |
| out: |
| put_dirh(dirh, res); |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static TEE_Result ree_fs_opendir_rpc(const TEE_UUID *uuid, |
| struct tee_fs_dir **dir) |
| |
| { |
| TEE_Result res; |
| struct tee_fs_dir *d = calloc(1, sizeof(*d)); |
| |
| if (!d) |
| return TEE_ERROR_OUT_OF_MEMORY; |
| |
| d->uuid = uuid; |
| |
| mutex_lock(&ree_fs_mutex); |
| |
| res = get_dirh(&d->dirh); |
| if (res) |
| goto out; |
| |
| /* See that there's at least one file */ |
| d->idx = -1; |
| d->d.oidlen = sizeof(d->d.oid); |
| res = tee_fs_dirfile_get_next(d->dirh, d->uuid, &d->idx, d->d.oid, |
| &d->d.oidlen); |
| d->idx = -1; |
| |
| out: |
| if (!res) { |
| *dir = d; |
| } else { |
| if (d) |
| put_dirh(d->dirh, false); |
| free(d); |
| } |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| static void ree_fs_closedir_rpc(struct tee_fs_dir *d) |
| { |
| if (d) { |
| mutex_lock(&ree_fs_mutex); |
| |
| put_dirh(d->dirh, false); |
| free(d); |
| |
| mutex_unlock(&ree_fs_mutex); |
| } |
| } |
| |
| static TEE_Result ree_fs_readdir_rpc(struct tee_fs_dir *d, |
| struct tee_fs_dirent **ent) |
| { |
| TEE_Result res; |
| |
| mutex_lock(&ree_fs_mutex); |
| |
| d->d.oidlen = sizeof(d->d.oid); |
| res = tee_fs_dirfile_get_next(d->dirh, d->uuid, &d->idx, d->d.oid, |
| &d->d.oidlen); |
| if (res == TEE_SUCCESS) |
| *ent = &d->d; |
| |
| mutex_unlock(&ree_fs_mutex); |
| |
| return res; |
| } |
| |
| const struct tee_file_operations ree_fs_ops = { |
| .open = ree_fs_open, |
| .create = ree_fs_create, |
| .close = ree_fs_close, |
| .read = ree_fs_read, |
| .write = ree_fs_write, |
| .truncate = ree_fs_truncate, |
| .rename = ree_fs_rename, |
| .remove = ree_fs_remove, |
| .opendir = ree_fs_opendir_rpc, |
| .closedir = ree_fs_closedir_rpc, |
| .readdir = ree_fs_readdir_rpc, |
| }; |