| // SPDX-License-Identifier: BSD-2-Clause |
| /* |
| * Copyright (c) 2017, Linaro Limited |
| */ |
| |
| #include <assert.h> |
| #include <bitstring.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <tee/fs_dirfile.h> |
| #include <types_ext.h> |
| |
| struct tee_fs_dirfile_dirh { |
| const struct tee_fs_dirfile_operations *fops; |
| struct tee_file_handle *fh; |
| int nbits; |
| bitstr_t *files; |
| size_t ndents; |
| }; |
| |
| struct dirfile_entry { |
| TEE_UUID uuid; |
| uint8_t oid[TEE_OBJECT_ID_MAX_LEN]; |
| uint32_t oidlen; |
| uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; |
| uint32_t file_number; |
| }; |
| |
| /* |
| * File layout |
| * |
| * dirfile_entry.0 |
| * ... |
| * dirfile_entry.n |
| * |
| * where n the index is disconnected from file_number in struct dirfile_entry |
| */ |
| |
| static TEE_Result maybe_grow_files(struct tee_fs_dirfile_dirh *dirh, int idx) |
| { |
| void *p; |
| |
| if (idx < dirh->nbits) |
| return TEE_SUCCESS; |
| |
| p = realloc(dirh->files, bitstr_size(idx + 1)); |
| if (!p) |
| return TEE_ERROR_OUT_OF_MEMORY; |
| dirh->files = p; |
| |
| bit_nclear(dirh->files, dirh->nbits, idx); |
| dirh->nbits = idx + 1; |
| |
| return TEE_SUCCESS; |
| } |
| |
| static TEE_Result set_file(struct tee_fs_dirfile_dirh *dirh, int idx) |
| { |
| TEE_Result res = maybe_grow_files(dirh, idx); |
| |
| if (!res) |
| bit_set(dirh->files, idx); |
| |
| return res; |
| } |
| |
| static void clear_file(struct tee_fs_dirfile_dirh *dirh, int idx) |
| { |
| if (idx < dirh->nbits) |
| bit_clear(dirh->files, idx); |
| } |
| |
| static bool test_file(struct tee_fs_dirfile_dirh *dirh, int idx) |
| { |
| if (idx < dirh->nbits) |
| return bit_test(dirh->files, idx); |
| |
| return false; |
| } |
| |
| static TEE_Result read_dent(struct tee_fs_dirfile_dirh *dirh, int idx, |
| struct dirfile_entry *dent) |
| { |
| TEE_Result res; |
| size_t l; |
| |
| l = sizeof(*dent); |
| res = dirh->fops->read(dirh->fh, sizeof(struct dirfile_entry) * idx, |
| dent, &l); |
| if (!res && l != sizeof(*dent)) |
| res = TEE_ERROR_ITEM_NOT_FOUND; |
| |
| return res; |
| } |
| |
| static TEE_Result write_dent(struct tee_fs_dirfile_dirh *dirh, size_t n, |
| struct dirfile_entry *dent) |
| { |
| TEE_Result res; |
| |
| res = dirh->fops->write(dirh->fh, sizeof(*dent) * n, |
| dent, sizeof(*dent)); |
| if (!res && n >= dirh->ndents) |
| dirh->ndents = n + 1; |
| |
| return res; |
| } |
| |
| TEE_Result tee_fs_dirfile_open(bool create, uint8_t *hash, |
| const struct tee_fs_dirfile_operations *fops, |
| struct tee_fs_dirfile_dirh **dirh_ret) |
| { |
| TEE_Result res; |
| struct tee_fs_dirfile_dirh *dirh = calloc(1, sizeof(*dirh)); |
| size_t n; |
| |
| if (!dirh) |
| return TEE_ERROR_OUT_OF_MEMORY; |
| |
| dirh->fops = fops; |
| res = fops->open(create, hash, NULL, NULL, &dirh->fh); |
| if (res) |
| goto out; |
| |
| for (n = 0;; n++) { |
| struct dirfile_entry dent; |
| |
| res = read_dent(dirh, n, &dent); |
| if (res) { |
| if (res == TEE_ERROR_ITEM_NOT_FOUND) |
| res = TEE_SUCCESS; |
| goto out; |
| } |
| |
| if (!dent.oidlen) |
| continue; |
| |
| if (test_file(dirh, dent.file_number)) { |
| DMSG("clearing duplicate file number %" PRIu32, |
| dent.file_number); |
| memset(&dent, 0, sizeof(dent)); |
| res = write_dent(dirh, n, &dent); |
| if (res) |
| goto out; |
| continue; |
| } |
| |
| res = set_file(dirh, dent.file_number); |
| if (res != TEE_SUCCESS) |
| goto out; |
| } |
| out: |
| if (!res) { |
| dirh->ndents = n; |
| *dirh_ret = dirh; |
| } else { |
| tee_fs_dirfile_close(dirh); |
| } |
| return res; |
| } |
| |
| void tee_fs_dirfile_close(struct tee_fs_dirfile_dirh *dirh) |
| { |
| if (dirh) { |
| dirh->fops->close(dirh->fh); |
| free(dirh->files); |
| free(dirh); |
| } |
| } |
| |
| TEE_Result tee_fs_dirfile_commit_writes(struct tee_fs_dirfile_dirh *dirh, |
| uint8_t *hash) |
| { |
| return dirh->fops->commit_writes(dirh->fh, hash); |
| } |
| |
| TEE_Result tee_fs_dirfile_get_tmp(struct tee_fs_dirfile_dirh *dirh, |
| struct tee_fs_dirfile_fileh *dfh) |
| { |
| TEE_Result res; |
| int i = 0; |
| |
| if (dirh->files) { |
| bit_ffc(dirh->files, dirh->nbits, &i); |
| if (i == -1) |
| i = dirh->nbits; |
| } |
| |
| res = set_file(dirh, i); |
| if (!res) |
| dfh->file_number = i; |
| |
| return res; |
| } |
| |
| TEE_Result tee_fs_dirfile_find(struct tee_fs_dirfile_dirh *dirh, |
| const TEE_UUID *uuid, const void *oid, |
| size_t oidlen, struct tee_fs_dirfile_fileh *dfh) |
| { |
| TEE_Result res; |
| struct dirfile_entry dent; |
| int n; |
| int first_free = -1; |
| |
| for (n = 0;; n++) { |
| res = read_dent(dirh, n, &dent); |
| if (res == TEE_ERROR_ITEM_NOT_FOUND && !oidlen) { |
| memset(&dent, 0, sizeof(dent)); |
| if (first_free != -1) |
| n = first_free; |
| break; |
| } |
| if (res) |
| return res; |
| |
| /* TODO check this loop when oidlen == 0 */ |
| |
| if (!dent.oidlen && first_free == -1) |
| first_free = n; |
| if (dent.oidlen != oidlen) |
| continue; |
| |
| assert(!oidlen || !dent.oidlen || |
| test_file(dirh, dent.file_number)); |
| |
| if (!memcmp(&dent.uuid, uuid, sizeof(dent.uuid)) && |
| !memcmp(&dent.oid, oid, oidlen)) |
| break; |
| } |
| |
| if (dfh) { |
| dfh->idx = n; |
| dfh->file_number = dent.file_number; |
| memcpy(dfh->hash, dent.hash, sizeof(dent.hash)); |
| } |
| |
| return TEE_SUCCESS; |
| } |
| |
| TEE_Result tee_fs_dirfile_fileh_to_fname(const struct tee_fs_dirfile_fileh *dfh, |
| char *fname, size_t *fnlen) |
| { |
| int r; |
| size_t l = *fnlen; |
| |
| if (dfh) |
| r = snprintf(fname, l, "%" PRIx32, dfh->file_number); |
| else |
| r = snprintf(fname, l, "dirf.db"); |
| |
| if (r < 0) |
| return TEE_ERROR_GENERIC; |
| |
| *fnlen = r + 1; |
| if ((size_t)r >= l) |
| return TEE_ERROR_SHORT_BUFFER; |
| |
| return TEE_SUCCESS; |
| } |
| |
| TEE_Result tee_fs_dirfile_rename(struct tee_fs_dirfile_dirh *dirh, |
| const TEE_UUID *uuid, |
| struct tee_fs_dirfile_fileh *dfh, |
| const void *oid, size_t oidlen) |
| { |
| TEE_Result res; |
| struct dirfile_entry dent; |
| |
| if (!oidlen || oidlen > sizeof(dent.oid)) |
| return TEE_ERROR_BAD_PARAMETERS; |
| memset(&dent, 0, sizeof(dent)); |
| dent.uuid = *uuid; |
| memcpy(dent.oid, oid, oidlen); |
| dent.oidlen = oidlen; |
| memcpy(dent.hash, dfh->hash, sizeof(dent.hash)); |
| dent.file_number = dfh->file_number; |
| |
| if (dfh->idx < 0) { |
| struct tee_fs_dirfile_fileh dfh2; |
| |
| res = tee_fs_dirfile_find(dirh, uuid, oid, oidlen, &dfh2); |
| if (res) { |
| if (res == TEE_ERROR_ITEM_NOT_FOUND) |
| res = tee_fs_dirfile_find(dirh, uuid, NULL, 0, |
| &dfh2); |
| if (res) |
| return res; |
| } |
| dfh->idx = dfh2.idx; |
| } |
| |
| return write_dent(dirh, dfh->idx, &dent); |
| } |
| |
| TEE_Result tee_fs_dirfile_remove(struct tee_fs_dirfile_dirh *dirh, |
| const struct tee_fs_dirfile_fileh *dfh) |
| { |
| TEE_Result res; |
| struct dirfile_entry dent; |
| uint32_t file_number; |
| |
| res = read_dent(dirh, dfh->idx, &dent); |
| if (res) |
| return res; |
| |
| if (!dent.oidlen) |
| return TEE_SUCCESS; |
| |
| file_number = dent.file_number; |
| assert(dfh->file_number == file_number); |
| assert(test_file(dirh, file_number)); |
| |
| memset(&dent, 0, sizeof(dent)); |
| res = write_dent(dirh, dfh->idx, &dent); |
| if (!res) |
| clear_file(dirh, file_number); |
| |
| return res; |
| } |
| |
| TEE_Result tee_fs_dirfile_update_hash(struct tee_fs_dirfile_dirh *dirh, |
| const struct tee_fs_dirfile_fileh *dfh) |
| { |
| TEE_Result res; |
| struct dirfile_entry dent; |
| |
| res = read_dent(dirh, dfh->idx, &dent); |
| if (res) |
| return res; |
| assert(dent.file_number == dfh->file_number); |
| assert(test_file(dirh, dent.file_number)); |
| |
| memcpy(&dent.hash, dfh->hash, sizeof(dent.hash)); |
| |
| return write_dent(dirh, dfh->idx, &dent); |
| } |
| |
| TEE_Result tee_fs_dirfile_get_next(struct tee_fs_dirfile_dirh *dirh, |
| const TEE_UUID *uuid, int *idx, void *oid, |
| size_t *oidlen) |
| { |
| TEE_Result res; |
| int i = *idx + 1; |
| struct dirfile_entry dent; |
| |
| if (i < 0) |
| i = 0; |
| |
| for (;; i++) { |
| res = read_dent(dirh, i, &dent); |
| if (res) |
| return res; |
| if (!memcmp(&dent.uuid, uuid, sizeof(dent.uuid)) && |
| dent.oidlen) |
| break; |
| } |
| |
| if (*oidlen < dent.oidlen) |
| return TEE_ERROR_SHORT_BUFFER; |
| |
| memcpy(oid, dent.oid, dent.oidlen); |
| *oidlen = dent.oidlen; |
| *idx = i; |
| |
| return TEE_SUCCESS; |
| } |