/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include "client.h" #include #include #include #include #include #include #include #include "client_session.h" #include "file.h" #include "session.h" #include "storage_limits.h" // macros to help manage debug output #define SS_ERR(args...) fprintf(stderr, "ss: " args) #if 0 // this can generate a lot of spew on debug builds #define SS_INFO(args...) fprintf(stderr, "ss: " args) #else #define SS_INFO(args...) \ do { \ } while (0) #endif /** * checkpoint_update_allowed - Is checkpoint modification currently allowed? * @transaction: Transaction object. * * Check if the given transaction is allowed to create a new or update the * existing checkpoint for its file system. Checkpoint updates may only be * requested by client while the device is in provisioning mode. * * Returns %true if a transaction may update the current checkpoint state, * %false otherwise. */ static bool checkpoint_update_allowed(struct transaction* tr) { return system_state_provisioning_allowed(); } /* * Legal secure storage directory and file names contain only * characters from the following set: [a-z][A-Z][0-9][.-_] * * It is not null terminated. */ static int is_valid_name(const char* name, size_t name_len) { size_t i; if (!name_len) return 0; for (i = 0; i < name_len; i++) { if ((name[i] >= 'a') && (name[i] <= 'z')) continue; if ((name[i] >= 'A') && (name[i] <= 'Z')) continue; if ((name[i] >= '0') && (name[i] <= '9')) continue; if ((name[i] == '.') || (name[i] == '-') || (name[i] == '_')) continue; // not a legal character so reject this name return 0; } return 1; } static int get_path(char* path_out, size_t path_out_size, const uuid_t* uuid, const char* file_name, size_t file_name_len) { unsigned int rc; rc = snprintf(path_out, path_out_size, "%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x/", uuid->time_low, uuid->time_mid, uuid->time_hi_and_version, uuid->clock_seq_and_node[0], uuid->clock_seq_and_node[1], uuid->clock_seq_and_node[2], uuid->clock_seq_and_node[3], uuid->clock_seq_and_node[4], uuid->clock_seq_and_node[5], uuid->clock_seq_and_node[6], uuid->clock_seq_and_node[7]); if (rc + file_name_len >= path_out_size) { return STORAGE_ERR_NOT_VALID; } memcpy(path_out + rc, file_name, file_name_len); path_out[rc + file_name_len] = '\0'; return STORAGE_NO_ERROR; } static enum storage_err file_op_result_to_storage_err( enum file_op_result result) { switch (result) { case FILE_OP_SUCCESS: return STORAGE_NO_ERROR; case FILE_OP_ERR_FAILED: // TODO: Consider returning STORAGE_ERR_TRANSACT consistently return STORAGE_ERR_GENERIC; case FILE_OP_ERR_EXIST: return STORAGE_ERR_EXIST; case FILE_OP_ERR_ALREADY_OPEN: return STORAGE_ERR_NOT_ALLOWED; case FILE_OP_ERR_NOT_FOUND: return STORAGE_ERR_NOT_FOUND; case FILE_OP_ERR_FS_REPAIRED: return STORAGE_ERR_FS_REPAIRED; } SS_ERR("%s: Unknown file_op_result: %d\n", __func__, result); return STORAGE_ERR_GENERIC; } static enum storage_err session_set_files_count( struct storage_client_session* session, size_t files_count) { struct storage_file_handle** files; if (files_count > STORAGE_MAX_OPEN_FILES) { SS_ERR("%s: too many open files\n", __func__); return STORAGE_ERR_NOT_VALID; } if (!files_count) { free(session->files); session->files = NULL; } else { files = realloc(session->files, sizeof(files[0]) * files_count); if (!files) { SS_ERR("%s: out of memory\n", __func__); return STORAGE_ERR_GENERIC; } if (files_count > session->files_count) memset(files + session->files_count, 0, sizeof(files[0]) * (files_count - session->files_count)); session->files = files; } session->files_count = files_count; SS_INFO("%s: new file table size, 0x%zx\n", __func__, files_count); return STORAGE_NO_ERROR; } static void session_shrink_files(struct storage_client_session* session) { uint32_t handle; handle = session->files_count; while (handle > 0 && !session->files[handle - 1]) handle--; if (handle < session->files_count) session_set_files_count(session, handle); } static void session_close_all_files(struct storage_client_session* session) { uint32_t f_handle; struct storage_file_handle* file; for (f_handle = 0; f_handle < session->files_count; f_handle++) { file = session->files[f_handle]; if (file) { file_close(file); free(file); } } if (session->files) { free(session->files); } session->files_count = 0; } static enum storage_err create_file_handle( struct storage_client_session* session, uint32_t* handlep, struct storage_file_handle** file_p) { enum storage_err result; uint32_t handle; struct storage_file_handle* file; for (handle = 0; handle < session->files_count; handle++) if (!session->files[handle]) break; if (handle >= session->files_count) { result = session_set_files_count(session, handle + 1); if (result != STORAGE_NO_ERROR) return result; } file = calloc(1, sizeof(*file)); if (!file) { SS_ERR("%s: out of memory\n", __func__); return STORAGE_ERR_GENERIC; } session->files[handle] = file; SS_INFO("%s: created file handle 0x%" PRIx32 "\n", __func__, handle); *handlep = handle; *file_p = file; return STORAGE_NO_ERROR; } static void free_file_handle(struct storage_client_session* session, uint32_t handle) { if (handle >= session->files_count) { SS_ERR("%s: invalid handle, 0x%" PRIx32 "\n", __func__, handle); return; } if (session->files[handle] == NULL) { SS_ERR("%s: closed handle, 0x%" PRIx32 "\n", __func__, handle); return; } free(session->files[handle]); session->files[handle] = NULL; SS_INFO("%s: deleted file handle 0x%" PRIx32 "\n", __func__, handle); session_shrink_files(session); } static struct storage_file_handle* get_file_handle( struct storage_client_session* session, uint32_t handle) { struct storage_file_handle* file; if (handle >= session->files_count) { SS_ERR("%s: invalid handle, 0x%" PRIx32 "\n", __func__, handle); return NULL; } file = session->files[handle]; if (!file) { SS_ERR("%s: closed handle, 0x%" PRIx32 "\n", __func__, handle); return NULL; } return file; } static enum storage_err assert_checkpoint_flag_valid( struct storage_client_session* session, struct storage_op_flags flags, const char* func_name) { if (flags.update_checkpoint) { if (!(flags.complete_transaction)) { SS_ERR("%s: STORAGE_MSG_FLAG_TRANSACT_CHECKPOINT cannot " "be used without STORAGE_MSG_FLAG_TRANSACT_COMPLETE\n", func_name); return STORAGE_ERR_NOT_VALID; } if (!checkpoint_update_allowed(&session->tr)) { SS_ERR("%s: Checkpoint requested but not currently allowed.\n", func_name); return STORAGE_ERR_NOT_ALLOWED; } } return STORAGE_NO_ERROR; } static enum storage_err ensure_active_transaction( struct storage_client_session* session, struct storage_op_flags flags) { if (session->tr.failed) { if (flags.complete_transaction) { /* last command in current transaction: * reset failed state and return error */ session->tr.failed = false; } return STORAGE_ERR_TRANSACT; } if (!transaction_is_active(&session->tr)) { /* previous transaction complete */ transaction_activate(&session->tr); } return STORAGE_NO_ERROR; } void storage_client_session_init(struct storage_client_session* session, struct fs* fs, const uuid_t* peer) { session->magic = STORAGE_CLIENT_SESSION_MAGIC; session->files = NULL; session->files_count = 0; transaction_init(&session->tr, fs, false); /* cache identity information */ memcpy(&session->uuid, peer, sizeof(*peer)); } void storage_client_session_destroy(struct storage_client_session* session) { if (list_in_list(&session->tr.allocated.node) && !session->tr.failed) { /* discard partial transaction */ transaction_fail(&session->tr); } session_close_all_files(session); transaction_free(&session->tr); } /* abort transaction and clear sticky transaction error */ enum storage_err storage_transaction_end(struct storage_client_session* session, struct storage_op_flags flags) { enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } if (flags.complete_transaction) { /* Allow checkpoint creation without an active transaction */ if (flags.update_checkpoint && !transaction_is_active(&session->tr)) { transaction_activate(&session->tr); } /* try to complete current transaction */ if (transaction_is_active(&session->tr)) { transaction_complete_etc(&session->tr, flags.update_checkpoint); } if (session->tr.failed) { SS_ERR("%s: failed to complete transaction\n", __func__); /* clear transaction failed state */ session->tr.failed = false; return STORAGE_ERR_TRANSACT; } return STORAGE_NO_ERROR; } /* discard current transaction */ if (transaction_is_active(&session->tr)) { transaction_fail(&session->tr); } /* clear transaction failed state */ session->tr.failed = false; return STORAGE_NO_ERROR; } enum storage_err storage_file_delete(struct storage_client_session* session, const char* fname, size_t fname_len, struct storage_op_flags flags) { enum file_op_result delete_res; char path_buf[FS_PATH_MAX]; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } /* make sure filename is legal */ if (!is_valid_name(fname, fname_len)) { SS_ERR("%s: invalid filename\n", __func__); return STORAGE_ERR_NOT_VALID; } result = get_path(path_buf, sizeof(path_buf), &session->uuid, fname, fname_len); if (result != STORAGE_NO_ERROR) { return result; } SS_INFO("%s: path %s\n", __func__, path_buf); delete_res = file_delete(&session->tr, path_buf, flags.allow_repaired); if (delete_res != FILE_OP_SUCCESS) { return file_op_result_to_storage_err(delete_res); } if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); if (session->tr.failed) { SS_ERR("%s: transaction commit failed\n", __func__); return STORAGE_ERR_GENERIC; } } return STORAGE_NO_ERROR; } /** * storage_file_check_name - Check if file handle matches path * @tr: Transaction object. * @file: File handle object. * @path: Path to check. * * Return: %true if @file matches @path, %false otherwise. */ static bool storage_file_check_name(struct transaction* tr, const struct storage_file_handle* file, const char* path) { bool ret; const struct file_info* file_info; struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); file_info = file_get_info(tr, &file->block_mac, &ref); if (!file_info) { printf("can't read file entry at %" PRIu64 "\n", block_mac_to_block(tr, &file->block_mac)); return false; } assert(file_info); ret = strcmp(file_info->path, path) == 0; file_info_put(file_info, &ref); return ret; } enum storage_err storage_file_move(struct storage_client_session* session, uint32_t handle, bool src_already_opened, const char* src_name, size_t src_name_len, const char* dst_name, size_t dst_name_len, enum file_create_mode dst_file_create_mode, struct storage_op_flags flags) { enum file_op_result open_result; enum file_op_result move_result; struct storage_file_handle* file = NULL; char path_buf[FS_PATH_MAX]; struct storage_file_handle tmp_file; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } /* make sure filenames are legal */ if (src_name && !is_valid_name(src_name, src_name_len)) { SS_ERR("%s: invalid src filename\n", __func__); return STORAGE_ERR_NOT_VALID; } if (!is_valid_name(dst_name, dst_name_len)) { SS_ERR("%s: invalid dst filename\n", __func__); return STORAGE_ERR_NOT_VALID; } if (!src_name && !src_already_opened) { SS_ERR("%s: src needs to be opened, but no src name provided\n", __func__); } if (src_already_opened) { file = get_file_handle(session, handle); if (!file) return STORAGE_ERR_NOT_VALID; } if (src_name) { result = get_path(path_buf, sizeof(path_buf), &session->uuid, src_name, src_name_len); if (result != STORAGE_NO_ERROR) { return result; } } SS_INFO("%s: src path %s\n", __func__, path_buf); if (file) { if (src_name && !storage_file_check_name(&session->tr, file, path_buf)) { return STORAGE_ERR_NOT_VALID; } } else { open_result = file_open(&session->tr, path_buf, &tmp_file, FILE_OPEN_NO_CREATE, flags.allow_repaired); if (open_result != FILE_OP_SUCCESS) { return file_op_result_to_storage_err(open_result); } file = &tmp_file; } result = get_path(path_buf, sizeof(path_buf), &session->uuid, dst_name, dst_name_len); if (result != STORAGE_NO_ERROR) { if (file == &tmp_file) { file_close(&tmp_file); } return result; } SS_INFO("%s: dst path %s\n", __func__, path_buf); move_result = file_move(&session->tr, file, path_buf, dst_file_create_mode, flags.allow_repaired); if (file == &tmp_file) { file_close(&tmp_file); } if (move_result != FILE_OP_SUCCESS) { return file_op_result_to_storage_err(move_result); } if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); if (session->tr.failed) { SS_ERR("%s: transaction commit failed\n", __func__); return STORAGE_ERR_GENERIC; } } return STORAGE_NO_ERROR; } enum storage_err storage_file_open(struct storage_client_session* session, const char* fname, size_t fname_len, enum file_create_mode file_create_mode, bool truncate, struct storage_op_flags flags, uint32_t* handle) { enum file_op_result open_result; struct storage_file_handle* file = NULL; uint32_t f_handle; char path_buf[FS_PATH_MAX]; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } /* make sure filename is legal */ if (!is_valid_name(fname, fname_len)) { SS_ERR("%s: invalid filename\n", __func__); return STORAGE_ERR_NOT_VALID; } result = get_path(path_buf, sizeof(path_buf), &session->uuid, fname, fname_len); if (result != STORAGE_NO_ERROR) { return result; } SS_INFO("%s: path %s (create_mode: %d, truncate: %d)\n", __func__, path_buf, file_create_mode, truncate); SS_INFO("%s: call create_file_handle\n", __func__); /* alloc file info struct */ result = create_file_handle(session, &f_handle, &file); if (result != STORAGE_NO_ERROR) { return result; } open_result = file_open(&session->tr, path_buf, file, file_create_mode, flags.allow_repaired); if (open_result != FILE_OP_SUCCESS) { result = file_op_result_to_storage_err(open_result); goto err_open_file; } if (truncate && file->size) { file_set_size(&session->tr, file, 0); } if (session->tr.failed) { SS_ERR("%s: transaction failed\n", __func__); result = STORAGE_ERR_GENERIC; goto err_transaction_failed; } if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); if (session->tr.failed) { SS_ERR("%s: transaction commit failed\n", __func__); result = STORAGE_ERR_GENERIC; goto err_transaction_failed; } } *handle = f_handle; return STORAGE_NO_ERROR; err_transaction_failed: file_close(file); err_open_file: free_file_handle(session, f_handle); return result; } enum storage_err storage_file_close(struct storage_client_session* session, uint32_t handle, struct storage_op_flags flags) { struct storage_file_handle* file; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } file = get_file_handle(session, handle); if (!file) { return STORAGE_ERR_NOT_VALID; } file_close(file); free_file_handle(session, handle); if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); if (session->tr.failed) { SS_ERR("%s: transaction commit failed\n", __func__); return STORAGE_ERR_GENERIC; } } return STORAGE_NO_ERROR; } enum storage_err storage_file_read(struct storage_client_session* session, uint32_t handle, uint32_t size, uint64_t offset, struct storage_op_flags flags, uint8_t* resp, size_t* resp_len) { void* bufp = resp; size_t buflen; size_t bytes_left, len; struct storage_file_handle* file; size_t block_size = get_file_block_size(session->tr.fs); data_block_t block_num; const uint8_t* block_data; struct obj_ref block_data_ref = OBJ_REF_INITIAL_VALUE(block_data_ref); size_t block_offset; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } file = get_file_handle(session, handle); if (!file) { SS_ERR("%s: invalid file handle (%" PRIx32 ")\n", __func__, handle); return STORAGE_ERR_NOT_VALID; } buflen = size; if (buflen > *resp_len) { SS_ERR("can't read more than %zu bytes, requested %zu\n", *resp_len, buflen); return STORAGE_ERR_NOT_VALID; } if (offset > file->size) { SS_ERR("can't read past end of file (%" PRIu64 " > %" PRIu64 ")\n", offset, file->size); return STORAGE_ERR_NOT_VALID; } /* calc number of bytes to read */ if ((offset + buflen) > file->size) { bytes_left = (size_t)(file->size - offset); } else { bytes_left = buflen; } buflen = bytes_left; /* save to return it to caller */ SS_INFO("%s: start 0x%" PRIx64 " cnt %zu\n", __func__, offset, bytes_left); while (bytes_left) { block_num = offset / block_size; block_data = file_get_block(&session->tr, file, block_num, &block_data_ref); block_offset = offset % block_size; len = (block_offset + bytes_left > block_size) ? block_size - block_offset : bytes_left; if (!block_data) { if (session->tr.failed) { SS_ERR("error reading block %" PRIu64 "\n", block_num); return STORAGE_ERR_GENERIC; } memset(bufp, 0, len); } else { memcpy(bufp, block_data + block_offset, len); file_block_put(block_data, &block_data_ref); } bytes_left -= len; offset += len; bufp += len; } *resp_len = buflen; return STORAGE_NO_ERROR; } static enum storage_err storage_create_gap( struct storage_client_session* session, struct storage_file_handle* file) { size_t block_size = get_file_block_size(session->tr.fs); data_block_t block_num; size_t block_offset; uint8_t* block_data; struct obj_ref block_data_ref = OBJ_REF_INITIAL_VALUE(block_data_ref); block_num = file->size / block_size; block_offset = file->size % block_size; if (block_offset) { /* * The file does not currently end on a block boundary. * We don't clear data in partial blocks when truncating * a file, so the last block could contain data that * should not be readable. We unconditionally clear the * exposed data when creating gaps in the file, as we * don't know if that data is already clear. */ block_data = file_get_block_write(&session->tr, file, block_num, true, &block_data_ref); if (!block_data) { SS_ERR("error getting block %" PRIu64 "\n", block_num); return STORAGE_ERR_GENERIC; } memset(block_data + block_offset, 0, block_size - block_offset); file_block_put_dirty(&session->tr, file, block_num, block_data, &block_data_ref); SS_INFO("%s: clear block at old size 0x%" PRIx64 ", block_offset 0x%zx\n", __func__, file->size, block_offset); } return STORAGE_NO_ERROR; } enum storage_err storage_file_write(struct storage_client_session* session, uint32_t handle, uint64_t offset, const uint8_t* data, size_t data_len, struct storage_op_flags flags) { size_t len; struct storage_file_handle* file; size_t block_size = get_file_block_size(session->tr.fs); data_block_t block_num; uint8_t* block_data; struct obj_ref block_data_ref = OBJ_REF_INITIAL_VALUE(block_data_ref); size_t block_offset; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } file = get_file_handle(session, handle); if (!file) { SS_ERR("%s: invalid file handle (%" PRIx32 ")\n", __func__, handle); return STORAGE_ERR_NOT_VALID; } if (offset > file->size) { result = storage_create_gap(session, file); if (result != STORAGE_NO_ERROR) { goto err_write; } } /* transfer data one ss block at a time */ while (data_len) { block_num = offset / block_size; block_offset = offset % block_size; len = (block_offset + data_len > block_size) ? block_size - block_offset : data_len; block_data = file_get_block_write(&session->tr, file, block_num, len != block_size, &block_data_ref); if (!block_data) { SS_ERR("error getting block %" PRIu64 "\n", block_num); result = STORAGE_ERR_GENERIC; goto err_write; } memcpy(block_data + block_offset, data, len); file_block_put_dirty(&session->tr, file, block_num, block_data, &block_data_ref); #if TLOG_LVL >= TLOG_LVL_DEBUG SS_INFO("%s: data %p offset 0x%" PRIx64 " len 0x%zx\n", __func__, data, offset, len); #endif offset += len; data += len; data_len -= len; } if (offset > file->size) { file_set_size(&session->tr, file, offset); } if (session->tr.failed) { SS_ERR("%s: transaction failed\n", __func__); return STORAGE_ERR_GENERIC; } if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); if (session->tr.failed) { SS_ERR("%s: transaction commit failed\n", __func__); return STORAGE_ERR_GENERIC; } } return STORAGE_NO_ERROR; err_write: if (!session->tr.failed) { transaction_fail(&session->tr); } err_transaction_complete: return result; } struct storage_file_list_state { struct file_iterate_state iter; char prefix[34]; size_t prefix_len; uint8_t max_count; uint8_t count; bool (*can_record_path)(void* callback_data, size_t max_path_len); void (*record_path)(void* callback_data, enum storage_file_list_flag flags, const char* path, size_t path_len); void* callback_data; }; static bool storage_file_list_buf_full(struct storage_file_list_state* miter) { if (miter->max_count && miter->count >= miter->max_count) { return true; } return !miter->can_record_path(miter->callback_data, FS_PATH_MAX - miter->prefix_len); } static void storage_file_list_add(struct storage_file_list_state* miter, enum storage_file_list_flag flags, const char* path) { assert(!storage_file_list_buf_full(miter)); size_t path_len = path ? strlen(path) : 0; assert(path_len <= FS_PATH_MAX - miter->prefix_len); miter->record_path(miter->callback_data, flags, path, path_len); miter->count++; } static bool storage_file_list_iter(struct file_iterate_state* iter, struct transaction* tr, const struct block_mac* block_mac, bool added, bool removed) { struct storage_file_list_state* miter = containerof(iter, struct storage_file_list_state, iter); const struct file_info* file_info; struct obj_ref ref = OBJ_REF_INITIAL_VALUE(ref); file_info = file_get_info(tr, block_mac, &ref); if (!file_info) { printf("can't read file entry at %" PRIu64 "\n", block_mac_to_block(tr, block_mac)); return true; } if (strncmp(file_info->path, miter->prefix, miter->prefix_len) == 0) { storage_file_list_add(miter, added ? STORAGE_FILE_LIST_ADDED : removed ? STORAGE_FILE_LIST_REMOVED : STORAGE_FILE_LIST_COMMITTED, file_info->path + miter->prefix_len); } file_info_put(file_info, &ref); return storage_file_list_buf_full(miter); } enum storage_err storage_file_list( struct storage_client_session* session, uint8_t max_count, enum storage_file_list_flag last_state, const char* last_fname, size_t last_fname_len, struct storage_op_flags flags, bool (*can_record_path)(void* callback_data, size_t max_path_len), void (*record_path)(void* callback_data, enum storage_file_list_flag flags, const char* path, size_t path_len), void* callback_data) { enum file_op_result iterate_res; const char* last_name; char path_buf[FS_PATH_MAX]; struct storage_file_list_state state = { .iter.file = storage_file_list_iter, .max_count = max_count, .record_path = record_path, .can_record_path = can_record_path, .callback_data = callback_data, }; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } result = get_path(state.prefix, sizeof(state.prefix), &session->uuid, "", 0); if (result != STORAGE_NO_ERROR) { SS_ERR("%s: internal error, get_path failed\n", __func__); return STORAGE_ERR_GENERIC; } state.prefix_len = strlen(state.prefix); if (last_state == STORAGE_FILE_LIST_END) { SS_ERR("%s: invalid request state (%" PRIx8 ")\n", __func__, last_state); return STORAGE_ERR_NOT_VALID; } if (last_state == STORAGE_FILE_LIST_START) { last_name = NULL; } else { /* make sure filename is legal */ if (!is_valid_name(last_fname, last_fname_len)) { SS_ERR("%s: invalid filename\n", __func__); return STORAGE_ERR_NOT_VALID; } result = get_path(path_buf, sizeof(path_buf), &session->uuid, last_fname, last_fname_len); if (result != STORAGE_NO_ERROR) { return result; } last_name = path_buf; } if (last_state != STORAGE_FILE_LIST_ADDED) { iterate_res = file_iterate(&session->tr, last_name, false, &state.iter, flags.allow_repaired); last_name = NULL; } else { iterate_res = FILE_OP_SUCCESS; } if (iterate_res == FILE_OP_SUCCESS && !storage_file_list_buf_full(&state)) { iterate_res = file_iterate(&session->tr, last_name, true, &state.iter, flags.allow_repaired); } if (iterate_res != FILE_OP_SUCCESS) { SS_ERR("%s: file_iterate failed\n", __func__); return file_op_result_to_storage_err(iterate_res); } if (!storage_file_list_buf_full(&state)) { storage_file_list_add(&state, STORAGE_FILE_LIST_END, NULL); } return STORAGE_NO_ERROR; } enum storage_err storage_file_get_size(struct storage_client_session* session, uint32_t handle, struct storage_op_flags flags, uint64_t* size) { bool valid; struct storage_file_handle* file; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } file = get_file_handle(session, handle); if (!file) { SS_ERR("%s: invalid file handle (%" PRIx32 ")\n", __func__, handle); return STORAGE_ERR_NOT_VALID; } valid = file_get_size(&session->tr, file, size); if (!valid) { return STORAGE_ERR_NOT_VALID; } return STORAGE_NO_ERROR; } enum storage_err storage_file_set_size(struct storage_client_session* session, uint32_t handle, uint64_t new_size, struct storage_op_flags flags) { struct storage_file_handle* file; enum storage_err result = assert_checkpoint_flag_valid(session, flags, __func__); if (result != STORAGE_NO_ERROR) { return result; } result = ensure_active_transaction(session, flags); if (result != STORAGE_NO_ERROR) { return result; } file = get_file_handle(session, handle); if (!file) { SS_ERR("%s: invalid file handle (%" PRIx32 ")\n", __func__, handle); return STORAGE_ERR_NOT_VALID; } SS_INFO("%s: new size 0x%" PRIx64 ", old size 0x%" PRIx64 "\n", __func__, new_size, file->size); /* for now we only support shrinking the file */ if (new_size > file->size) { result = storage_create_gap(session, file); if (result != STORAGE_NO_ERROR) { return result; } storage_create_gap(session, file); } /* check for nop */ if (new_size != file->size) { /* update size */ file_set_size(&session->tr, file, new_size); } /* try to commit */ if (flags.complete_transaction) { transaction_complete_etc(&session->tr, flags.update_checkpoint); } if (session->tr.failed) { SS_ERR("%s: transaction failed\n", __func__); return STORAGE_ERR_GENERIC; } return STORAGE_NO_ERROR; }