diff --git a/trusty/storage/lib/Android.mk b/trusty/storage/lib/Android.mk new file mode 100644 index 000000000..7e0fc9da4 --- /dev/null +++ b/trusty/storage/lib/Android.mk @@ -0,0 +1,37 @@ +# +# Copyright (C) 2015 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. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE := libtrustystorage + +LOCAL_SRC_FILES := \ + storage.c \ + +LOCAL_CLFAGS = -fvisibility=hidden -Wall -Werror + +LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include +LOCAL_C_INCLUDES := $(LOCAL_PATH)/include + +LOCAL_STATIC_LIBRARIES := \ + liblog \ + libtrusty \ + libtrustystorageinterface + +include $(BUILD_STATIC_LIBRARY) + diff --git a/trusty/storage/lib/include/trusty/lib/storage.h b/trusty/storage/lib/include/trusty/lib/storage.h new file mode 100644 index 000000000..b8ddf67d8 --- /dev/null +++ b/trusty/storage/lib/include/trusty/lib/storage.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2016 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. + */ + +#pragma once + +#include +#include + +#define STORAGE_MAX_NAME_LENGTH_BYTES 159 + +__BEGIN_DECLS + +typedef uint32_t storage_session_t; +typedef uint64_t file_handle_t; +typedef uint64_t storage_off_t; + +#define STORAGE_INVALID_SESSION ((storage_session_t)-1) + +/** + * storage_ops_flags - storage related operation flags + * @STORAGE_OP_COMPLETE: forces to commit current transaction + */ +enum storage_ops_flags { + STORAGE_OP_COMPLETE = 0x1, +}; + +/** + * storage_open_session() - Opens a storage session. + * @device: device node for talking with Trusty + * @session_p: pointer to location in which to store session handle + * in case of success. + * + * Return: 0 on success, or an error code < 0 on failure. + */ +int storage_open_session(const char *device, storage_session_t *session_p, const char *port); + +/** + * storage_close_session() - Closes the session. + * @session: the session to close + */ +void storage_close_session(storage_session_t session); + +/** + * storage_open_file() - Opens a file + * @session: the storage_session_t returned from a call to storage_open_session + * @handle_p: pointer to location in which to store file handle in case of success + * @name: a null-terminated string identifier of the file to open. + * Cannot be more than STORAGE_MAX_NAME_LENGTH_BYTES in length. + * @flags: A bitmask consisting any storage_file_flag value or'ed together: + * - STORAGE_FILE_OPEN_CREATE: if this file does not exist, create it. + * - STORAGE_FILE_OPEN_CREATE_EXCLUSIVE: when specified, opening file with + * STORAGE_OPEN_FILE_CREATE flag will + * fail if the file already exists. + * Only meaningful if used in combination + * with STORAGE_FILE_OPEN_CREATE flag. + * - STORAGE_FILE_OPEN_TRUNCATE: if this file already exists, discard existing + * content and open it as a new file. No change + * in semantics if the file does not exist. + * @opflags: a combination of @storage_op_flags + * + * Return: 0 on success, or an error code < 0 on failure. + */ +int storage_open_file(storage_session_t session, file_handle_t *handle_p, + const char *name, uint32_t flags, uint32_t opflags); + +/** + * storage_close_file() - Closes a file. + * @handle: the file_handle_t retrieved from storage_open_file + */ +void storage_close_file(file_handle_t handle); + +/** + * storage_delete_file - Deletes a file. + * @session: the storage_session_t returned from a call to storage_open_session + * @name: the name of the file to delete + * @opflags: a combination of @storage_op_flags + * + * Return: 0 on success, or an error code < 0 on failure. + */ +int storage_delete_file(storage_session_t session, const char *name, + uint32_t opflags); + +/** + * storage_read() - Reads a file at a given offset. + * @handle: the file_handle_t retrieved from storage_open_file + * @off: the start offset from whence to read in the file + * @buf: the buffer in which to write the data read + * @size: the size of buf and number of bytes to read + * + * Return: the number of bytes read on success, negative error code on failure + */ +ssize_t storage_read(file_handle_t handle, + storage_off_t off, void *buf, size_t size); + +/** + * storage_write() - Writes to a file at a given offset. Grows the file if necessary. + * @handle: the file_handle_t retrieved from storage_open_file + * @off: the start offset from whence to write in the file + * @buf: the buffer containing the data to write + * @size: the size of buf and number of bytes to write + * @opflags: a combination of @storage_op_flags + * + * Return: the number of bytes written on success, negative error code on failure + */ +ssize_t storage_write(file_handle_t handle, + storage_off_t off, const void *buf, size_t size, + uint32_t opflags); + +/** + * storage_set_file_size() - Sets the size of the file. + * @handle: the file_handle_t retrieved from storage_open_file + * @off: the number of bytes to set as the new size of the file + * @opflags: a combination of @storage_op_flags + * + * Return: 0 on success, negative error code on failure. + */ +int storage_set_file_size(file_handle_t handle, storage_off_t file_size, + uint32_t opflags); + +/** + * storage_get_file_size() - Gets the size of the file. + * @session: the storage_session_t returned from a call to storage_open_session + * @handle: the file_handle_t retrieved from storage_open_file + * @size: pointer to storage_off_t in which to store the file size + * + * Return: 0 on success, negative error code on failure. + */ +int storage_get_file_size(file_handle_t handle, storage_off_t *size); + + +/** + * storage_end_transaction: End current transaction + * @session: the storage_session_t returned from a call to storage_open_session + * @complete: if true, commit current transaction, discard it otherwise + * + * Return: 0 on success, negative error code on failure. + */ +int storage_end_transaction(storage_session_t session, bool complete); + + +__END_DECLS diff --git a/trusty/storage/lib/storage.c b/trusty/storage/lib/storage.c new file mode 100644 index 000000000..8130f769a --- /dev/null +++ b/trusty/storage/lib/storage.c @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2016 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 +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_TAG "trusty_storage_client" +#include + +#define MAX_CHUNK_SIZE 4040 + +static inline file_handle_t make_file_handle(storage_session_t s, uint32_t fid) +{ + return ((uint64_t)s << 32) | fid; +} + +static inline storage_session_t _to_session(file_handle_t fh) +{ + return (storage_session_t)(fh >> 32); +} + +static inline uint32_t _to_handle(file_handle_t fh) +{ + return (uint32_t) fh; +} + +static inline uint32_t _to_msg_flags(uint32_t opflags) +{ + uint32_t msg_flags = 0; + + if (opflags & STORAGE_OP_COMPLETE) + msg_flags |= STORAGE_MSG_FLAG_TRANSACT_COMPLETE; + + return msg_flags; +} + +static ssize_t check_response(struct storage_msg *msg, ssize_t res) +{ + if (res < 0) + return res; + + if ((size_t)res < sizeof(*msg)) { + ALOGE("invalid msg length (%zd < %zd)\n", res, sizeof(*msg)); + return -EIO; + } + + ALOGV("cmd 0x%x: server returned %u\n", msg->cmd, msg->result); + + switch(msg->result) { + case STORAGE_NO_ERROR: + return res - sizeof(*msg); + + case STORAGE_ERR_NOT_FOUND: + return -ENOENT; + + case STORAGE_ERR_EXIST: + return -EEXIST; + + case STORAGE_ERR_NOT_VALID: + return -EINVAL; + + case STORAGE_ERR_UNIMPLEMENTED: + ALOGE("cmd 0x%x: is unhandles command\n", msg->cmd); + return -EINVAL; + + case STORAGE_ERR_ACCESS: + return -EACCES; + + case STORAGE_ERR_TRANSACT: + return -EBUSY; + + case STORAGE_ERR_GENERIC: + ALOGE("cmd 0x%x: internal server error\n", msg->cmd); + return -EIO; + + default: + ALOGE("cmd 0x%x: unhandled server response %u\n", + msg->cmd, msg->result); + } + + return -EIO; +} + +static ssize_t send_reqv(storage_session_t session, + const struct iovec *tx_iovs, uint tx_iovcnt, + const struct iovec *rx_iovs, uint rx_iovcnt) +{ + ssize_t rc; + + rc = writev(session, tx_iovs, tx_iovcnt); + if (rc < 0) { + rc = -errno; + ALOGE("failed to send request: %s\n", strerror(errno)); + return rc; + } + + rc = readv(session, rx_iovs, rx_iovcnt); + if (rc < 0) { + rc = -errno; + ALOGE("failed to recv response: %s\n", strerror(errno)); + return rc; + } + + return rc; +} + +int storage_open_session(const char *device, storage_session_t *session_p, + const char *port) +{ + int rc = tipc_connect(device, port); + if (rc < 0) + return rc; + *session_p = (storage_session_t) rc; + return 0; +} + +void storage_close_session(storage_session_t session) +{ + tipc_close(session); +} + + +int storage_open_file(storage_session_t session, file_handle_t *handle_p, const char *name, + uint32_t flags, uint32_t opflags) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_OPEN, .flags = _to_msg_flags(opflags)}; + struct storage_file_open_req req = { .flags = flags }; + struct iovec tx[3] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}, {(void *)name, strlen(name)}}; + struct storage_file_open_resp rsp = { 0 }; + struct iovec rx[2] = {{&msg, sizeof(msg)}, {&rsp, sizeof(rsp)}}; + + ssize_t rc = send_reqv(session, tx, 3, rx, 2); + rc = check_response(&msg, rc); + if (rc < 0) + return rc; + + if ((size_t)rc != sizeof(rsp)) { + ALOGE("%s: invalid response length (%zd != %zd)\n", __func__, rc, sizeof(rsp)); + return -EIO; + } + + *handle_p = make_file_handle(session, rsp.handle); + return 0; +} + +void storage_close_file(file_handle_t fh) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_CLOSE }; + struct storage_file_close_req req = { .handle = _to_handle(fh)}; + struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}}; + struct iovec rx[1] = {{&msg, sizeof(msg)}}; + + ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 1); + rc = check_response(&msg, rc); + if (rc < 0) { + ALOGE("close file failed (%d)\n", (int)rc); + } +} + +int storage_delete_file(storage_session_t session, const char *name, uint32_t opflags) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_DELETE, .flags = _to_msg_flags(opflags)}; + struct storage_file_delete_req req = { .flags = 0, }; + struct iovec tx[3] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}, {(void *)name, strlen(name)}}; + struct iovec rx[1] = {{&msg, sizeof(msg)}}; + + ssize_t rc = send_reqv(session, tx, 3, rx, 1); + return check_response(&msg, rc); +} + +static int _read_chunk(file_handle_t fh, storage_off_t off, void *buf, size_t size) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_READ }; + struct storage_file_read_req req = { .handle = _to_handle(fh), .size = size, .offset = off }; + struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}}; + struct iovec rx[2] = {{&msg, sizeof(msg)}, {buf, size}}; + + ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 2); + return check_response(&msg, rc); +} + +ssize_t storage_read(file_handle_t fh, storage_off_t off, void *buf, size_t size) +{ + int rc; + size_t bytes_read = 0; + size_t chunk = MAX_CHUNK_SIZE; + uint8_t *ptr = buf; + + while (size) { + if (chunk > size) + chunk = size; + rc = _read_chunk(fh, off, ptr, chunk); + if (rc < 0) + return rc; + if (rc == 0) + break; + off += rc; + ptr += rc; + bytes_read += rc; + size -= rc; + } + return bytes_read; +} + +static int _write_req(file_handle_t fh, storage_off_t off, + const void *buf, size_t size, uint32_t msg_flags) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_WRITE, .flags = msg_flags, }; + struct storage_file_write_req req = { .handle = _to_handle(fh), .offset = off, }; + struct iovec tx[3] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}, {(void *)buf, size}}; + struct iovec rx[1] = {{&msg, sizeof(msg)}}; + + ssize_t rc = send_reqv(_to_session(fh), tx, 3, rx, 1); + rc = check_response(&msg, rc); + return rc < 0 ? rc : size; +} + +ssize_t storage_write(file_handle_t fh, storage_off_t off, + const void *buf, size_t size, uint32_t opflags) +{ + int rc; + size_t bytes_written = 0; + size_t chunk = MAX_CHUNK_SIZE; + const uint8_t *ptr = buf; + uint32_t msg_flags = _to_msg_flags(opflags & ~STORAGE_OP_COMPLETE); + + while (size) { + if (chunk >= size) { + /* last chunk in sequence */ + chunk = size; + msg_flags = _to_msg_flags(opflags); + } + rc = _write_req(fh, off, ptr, chunk, msg_flags); + if (rc < 0) + return rc; + if ((size_t)rc != chunk) { + ALOGE("got partial write (%d)\n", (int)rc); + return -EIO; + } + off += chunk; + ptr += chunk; + bytes_written += chunk; + size -= chunk; + } + return bytes_written; +} + +int storage_set_file_size(file_handle_t fh, storage_off_t file_size, uint32_t opflags) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_SET_SIZE, .flags = _to_msg_flags(opflags)}; + struct storage_file_set_size_req req = { .handle = _to_handle(fh), .size = file_size, }; + struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}}; + struct iovec rx[1] = {{&msg, sizeof(msg)}}; + + ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 1); + return check_response(&msg, rc); +} + +int storage_get_file_size(file_handle_t fh, storage_off_t *size_p) +{ + struct storage_msg msg = { .cmd = STORAGE_FILE_GET_SIZE }; + struct storage_file_get_size_req req = { .handle = _to_handle(fh), }; + struct iovec tx[2] = {{&msg, sizeof(msg)}, {&req, sizeof(req)}}; + struct storage_file_get_size_resp rsp; + struct iovec rx[2] = {{&msg, sizeof(msg)}, {&rsp, sizeof(rsp)}}; + + ssize_t rc = send_reqv(_to_session(fh), tx, 2, rx, 2); + rc = check_response(&msg, rc); + if (rc < 0) + return rc; + + if ((size_t)rc != sizeof(rsp)) { + ALOGE("%s: invalid response length (%zd != %zd)\n", __func__, rc, sizeof(rsp)); + return -EIO; + } + + *size_p = rsp.size; + return 0; +} + +int storage_end_transaction(storage_session_t session, bool complete) +{ + struct storage_msg msg = { + .cmd = STORAGE_END_TRANSACTION, + .flags = complete ? STORAGE_MSG_FLAG_TRANSACT_COMPLETE : 0, + }; + struct iovec iov = {&msg, sizeof(msg)}; + + ssize_t rc = send_reqv(session, &iov, 1, &iov, 1); + return check_response(&msg, rc); +} diff --git a/trusty/storage/tests/Android.mk b/trusty/storage/tests/Android.mk new file mode 100644 index 000000000..71c904df1 --- /dev/null +++ b/trusty/storage/tests/Android.mk @@ -0,0 +1,29 @@ +# +# Copyright (C) 2016 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. +# + +LOCAL_PATH:= $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_MODULE := secure-storage-unit-test +LOCAL_CFLAGS += -g -Wall -Werror -std=gnu++11 -Wno-missing-field-initializers +LOCAL_STATIC_LIBRARIES := \ + libtrustystorageinterface \ + libtrustystorage \ + libtrusty \ + liblog +LOCAL_SRC_FILES := main.cpp +include $(BUILD_NATIVE_TEST) + diff --git a/trusty/storage/tests/main.cpp b/trusty/storage/tests/main.cpp new file mode 100644 index 000000000..a771b877d --- /dev/null +++ b/trusty/storage/tests/main.cpp @@ -0,0 +1,3040 @@ +/* + * Copyright (C) 2016 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 +#include +#include +#include + +#include + +#define TRUSTY_DEVICE_NAME "/dev/trusty-ipc-dev0" + +#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) + +static inline bool is_32bit_aligned(size_t sz) +{ + return ((sz & 0x3) == 0); +} + +static inline bool is_valid_size(size_t sz) { + return (sz > 0) && is_32bit_aligned(sz); +} + +static bool is_valid_offset(storage_off_t off) +{ + return (off & 0x3) == 0ULL; +} + +static void fill_pattern32(uint32_t *buf, size_t len, storage_off_t off) +{ + size_t cnt = len / sizeof(uint32_t); + uint32_t pattern = (uint32_t)(off / sizeof(uint32_t)); + while (cnt--) { + *buf++ = pattern++; + } +} + +static bool check_pattern32(const uint32_t *buf, size_t len, storage_off_t off) +{ + size_t cnt = len / sizeof(uint32_t); + uint32_t pattern = (uint32_t)(off / sizeof(uint32_t)); + while (cnt--) { + if (*buf != pattern) + return false; + buf++; + pattern++; + } + return true; +} + +static bool check_value32(const uint32_t *buf, size_t len, uint32_t val) +{ + size_t cnt = len / sizeof(uint32_t); + while (cnt--) { + if (*buf != val) + return false; + buf++; + } + return true; +} + +using testing::TestWithParam; + +class StorageServiceTest : public virtual TestWithParam { +public: + StorageServiceTest() {} + virtual ~StorageServiceTest() {} + + virtual void SetUp() { + port_ = GetParam(); + test_buf_ = NULL; + aux_session_ = STORAGE_INVALID_SESSION; + int rc = storage_open_session(TRUSTY_DEVICE_NAME, &session_, port_); + ASSERT_EQ(0, rc); + } + + virtual void TearDown() { + if (test_buf_) { + delete[] test_buf_; + test_buf_ = NULL; + } + storage_close_session(session_); + + if (aux_session_ != STORAGE_INVALID_SESSION) { + storage_close_session(aux_session_); + aux_session_ = STORAGE_INVALID_SESSION; + } + } + + void WriteReadAtOffsetHelper(file_handle_t handle, size_t blk, size_t cnt, bool complete); + + void WriteZeroChunk(file_handle_t handle, storage_off_t off, size_t chunk_len, bool complete ); + void WritePatternChunk(file_handle_t handle, storage_off_t off, size_t chunk_len, bool complete); + void WritePattern(file_handle_t handle, storage_off_t off, size_t data_len, size_t chunk_len, bool complete); + + void ReadChunk(file_handle_t handle, storage_off_t off, size_t chunk_len, + size_t head_len, size_t pattern_len, size_t tail_len); + void ReadPattern(file_handle_t handle, storage_off_t off, size_t data_len, size_t chunk_len); + void ReadPatternEOF(file_handle_t handle, storage_off_t off, size_t chunk_len, size_t exp_len); + +protected: + const char *port_; + uint32_t *test_buf_; + storage_session_t session_; + storage_session_t aux_session_; +}; + +INSTANTIATE_TEST_CASE_P(SS_TD_Tests, StorageServiceTest, ::testing::Values(STORAGE_CLIENT_TD_PORT)); +INSTANTIATE_TEST_CASE_P(SS_TDEA_Tests, StorageServiceTest, ::testing::Values(STORAGE_CLIENT_TDEA_PORT)); +INSTANTIATE_TEST_CASE_P(SS_TP_Tests, StorageServiceTest, ::testing::Values(STORAGE_CLIENT_TP_PORT)); + + +void StorageServiceTest::WriteZeroChunk(file_handle_t handle, storage_off_t off, + size_t chunk_len, bool complete) +{ + int rc; + uint32_t data_buf[chunk_len/sizeof(uint32_t)]; + + ASSERT_PRED1(is_valid_size, chunk_len); + ASSERT_PRED1(is_valid_offset, off); + + memset(data_buf, 0, chunk_len); + + rc = storage_write(handle, off, data_buf, sizeof(data_buf), + complete ? STORAGE_OP_COMPLETE : 0); + ASSERT_EQ((int)chunk_len, rc); +} + +void StorageServiceTest::WritePatternChunk(file_handle_t handle, storage_off_t off, + size_t chunk_len, bool complete) +{ + int rc; + uint32_t data_buf[chunk_len/sizeof(uint32_t)]; + + ASSERT_PRED1(is_valid_size, chunk_len); + ASSERT_PRED1(is_valid_offset, off); + + fill_pattern32(data_buf, chunk_len, off); + + rc = storage_write(handle, off, data_buf, sizeof(data_buf), + complete ? STORAGE_OP_COMPLETE : 0); + ASSERT_EQ((int)chunk_len, rc); +} + +void StorageServiceTest::WritePattern(file_handle_t handle, storage_off_t off, + size_t data_len, size_t chunk_len, bool complete) +{ + ASSERT_PRED1(is_valid_size, data_len); + ASSERT_PRED1(is_valid_size, chunk_len); + + while (data_len) { + if (data_len < chunk_len) + chunk_len = data_len; + WritePatternChunk(handle, off, chunk_len, (chunk_len == data_len) && complete); + ASSERT_FALSE(HasFatalFailure()); + off += chunk_len; + data_len -= chunk_len; + } +} + +void StorageServiceTest::ReadChunk(file_handle_t handle, + storage_off_t off, size_t chunk_len, + size_t head_len, size_t pattern_len, + size_t tail_len) +{ + int rc; + uint32_t data_buf[chunk_len/sizeof(uint32_t)]; + uint8_t *data_ptr = (uint8_t *)data_buf; + + ASSERT_PRED1(is_valid_size, chunk_len); + ASSERT_PRED1(is_valid_offset, off); + ASSERT_EQ(head_len + pattern_len + tail_len, chunk_len); + + rc = storage_read(handle, off, data_buf, chunk_len); + ASSERT_EQ((int)chunk_len, rc); + + if (head_len) { + ASSERT_TRUE(check_value32((const uint32_t *)data_ptr, head_len, 0)); + data_ptr += head_len; + off += head_len; + } + + if (pattern_len) { + ASSERT_TRUE(check_pattern32((const uint32_t *)data_ptr, pattern_len, off)); + data_ptr += pattern_len; + } + + if (tail_len) { + ASSERT_TRUE(check_value32((const uint32_t *)data_ptr, tail_len, 0)); + } +} + +void StorageServiceTest::ReadPattern(file_handle_t handle, storage_off_t off, + size_t data_len, size_t chunk_len) +{ + int rc; + uint32_t data_buf[chunk_len/sizeof(uint32_t)]; + + ASSERT_PRED1(is_valid_size, chunk_len); + ASSERT_PRED1(is_valid_size, data_len); + ASSERT_PRED1(is_valid_offset, off); + + while (data_len) { + if (chunk_len > data_len) + chunk_len = data_len; + rc = storage_read(handle, off, data_buf, sizeof(data_buf)); + ASSERT_EQ((int)chunk_len, rc); + ASSERT_TRUE(check_pattern32(data_buf, chunk_len, off)); + off += chunk_len; + data_len -= chunk_len; + } +} + +void StorageServiceTest::ReadPatternEOF(file_handle_t handle, storage_off_t off, + size_t chunk_len, size_t exp_len) +{ + int rc; + size_t bytes_read = 0; + uint32_t data_buf[chunk_len/sizeof(uint32_t)]; + + ASSERT_PRED1(is_valid_size, chunk_len); + ASSERT_PRED1(is_32bit_aligned, exp_len); + + while (true) { + rc = storage_read(handle, off, data_buf, sizeof(data_buf)); + ASSERT_GE(rc, 0); + if (rc == 0) + break; // end of file reached + ASSERT_PRED1(is_valid_size, (size_t)rc); + ASSERT_TRUE(check_pattern32(data_buf, rc, off)); + off += rc; + bytes_read += rc; + } + ASSERT_EQ(bytes_read, exp_len); +} + +TEST_P(StorageServiceTest, CreateDelete) { + int rc; + file_handle_t handle; + const char *fname = "test_create_delete_file"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // one more time (expect -ENOENT only) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); + + // create file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // try to create it again while it is still opened (expect -EEXIST) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EEXIST, rc); + + // close it + storage_close_file(handle); + + // try to create it again while it is closed (expect -EEXIST) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EEXIST, rc); + + // delete file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // one more time (expect -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); +} + + +TEST_P(StorageServiceTest, DeleteOpened) { + int rc; + file_handle_t handle; + const char *fname = "delete_opened_test_file"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // one more time (expect -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); + + // open/create file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // delete opened file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // one more time (expect -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); + + // close file + storage_close_file(handle); + + // one more time (expect -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); +} + + +TEST_P(StorageServiceTest, OpenNoCreate) { + int rc; + file_handle_t handle; + const char *fname = "test_open_no_create_file"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // open non-existing file (expect -ENOENT) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // create file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // open existing file (expect 0) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // close it + storage_close_file(handle); + + // delete file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); +} + + +TEST_P(StorageServiceTest, OpenOrCreate) { + int rc; + file_handle_t handle; + const char *fname = "test_open_create_file"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // open/create a non-existing file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // open/create an existing file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // delete file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); +} + + +TEST_P(StorageServiceTest, OpenCreateDeleteCharset) { + int rc; + file_handle_t handle; + const char *fname = "ABCDEFGHIJKLMNOPQRSTUVWXYZ-abcdefghijklmnopqrstuvwxyz_01234.56789"; + + // open/create file (expect 0) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // open/create an existing file (expect 0) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // delete file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open again + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); +} + + +TEST_P(StorageServiceTest, WriteReadSequential) { + int rc; + size_t blk = 2048; + file_handle_t handle; + const char *fname = "test_write_read_sequential"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // create file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks (sequentially) + WritePattern(handle, 0, 32 * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + ReadPattern(handle, 0, 32 * blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // close file + storage_close_file(handle); + + // open the same file again + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // read data back (sequentially) and check pattern again + ReadPattern(handle, 0, 32 * blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, OpenTruncate) { + int rc; + uint32_t val; + size_t blk = 2048; + file_handle_t handle; + const char *fname = "test_open_truncate"; + + // make sure test file does not exist (expect success or -ENOENT) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); + + // create file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write some data and read it back + WritePatternChunk(handle, 0, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + ReadPattern(handle, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // close file + storage_close_file(handle); + + // reopen with truncate + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_TRUNCATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + /* try to read data back (expect no data) */ + rc = storage_read(handle, 0LL, &val, sizeof(val)); + ASSERT_EQ(0, rc); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, OpenSame) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + file_handle_t handle3; + const char *fname = "test_open_same_file"; + + // open/create file (expect 0) + rc = storage_open_file(session_, &handle1, fname, STORAGE_FILE_OPEN_CREATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + storage_close_file(handle1); + + // open an existing file first time (expect 0) + rc = storage_open_file(session_, &handle1, fname, 0, 0); + ASSERT_EQ(0, rc); + + // open the same file second time (expect error) + rc = storage_open_file(session_, &handle2, fname, 0, 0); + ASSERT_NE(0, rc); + + storage_close_file(handle1); + + // delete file (expect 0) + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open deleted file (expect -ENOENT) + rc = storage_open_file(session_, &handle3, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); +} + + +TEST_P(StorageServiceTest, OpenMany) { + int rc; + file_handle_t handles[10]; + char filename[10]; + const char *fname_fmt = "mf%d"; + + // open or create a bunch of files (expect 0) + for (uint i = 0; i < ARRAY_SIZE(handles); ++i) { + snprintf(filename, sizeof(filename), fname_fmt, i); + rc = storage_open_file(session_, &handles[i], filename, + STORAGE_FILE_OPEN_CREATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + } + + // check that all handles are different + for (uint i = 0; i < ARRAY_SIZE(handles)-1; i++) { + for (uint j = i+1; j < ARRAY_SIZE(handles); j++) { + ASSERT_NE(handles[i], handles[j]); + } + } + + // close them all + for (uint i = 0; i < ARRAY_SIZE(handles); ++i) { + storage_close_file(handles[i]); + } + + // open all files without CREATE flags (expect 0) + for (uint i = 0; i < ARRAY_SIZE(handles); ++i) { + snprintf(filename, sizeof(filename), fname_fmt, i); + rc = storage_open_file(session_, &handles[i], filename, 0, 0); + ASSERT_EQ(0, rc); + } + + // check that all handles are different + for (uint i = 0; i < ARRAY_SIZE(handles)-1; i++) { + for (uint j = i+1; j < ARRAY_SIZE(handles); j++) { + ASSERT_NE(handles[i], handles[j]); + } + } + + // close and remove all test files + for (uint i = 0; i < ARRAY_SIZE(handles); ++i) { + storage_close_file(handles[i]); + snprintf(filename, sizeof(filename), fname_fmt, i); + rc = storage_delete_file(session_, filename, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + } +} + + +TEST_P(StorageServiceTest, ReadAtEOF) { + int rc; + uint32_t val; + size_t blk = 2048; + file_handle_t handle; + const char *fname = "test_read_eof"; + + // open/create/truncate file + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write block at offset 0 + WritePatternChunk(handle, 0, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close file + storage_close_file(handle); + + // open same file again + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // read the whole block back and check pattern again + ReadPattern(handle, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // read at end of file (expected 0 bytes) + rc = storage_read(handle, blk, &val, sizeof(val)); + ASSERT_EQ(0, rc); + + // partial read at end of the file (expected partial data) + ReadPatternEOF(handle, blk/2, blk, blk/2); + ASSERT_FALSE(HasFatalFailure()); + + // read past end of file + rc = storage_read(handle, blk + 2, &val, sizeof(val)); + ASSERT_EQ(-EINVAL, rc); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, GetFileSize) { + int rc; + size_t blk = 2048; + storage_off_t size; + file_handle_t handle; + const char *fname = "test_get_file_size"; + + // open/create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check file size (expect success and size == 0) + size = 1; + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, size); + + // write block + WritePatternChunk(handle, 0, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check size + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ(blk, size); + + // write another block + WritePatternChunk(handle, blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check size again + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ(blk*2, size); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, SetFileSize) { + int rc; + size_t blk = 2048; + storage_off_t size; + file_handle_t handle; + const char *fname = "test_set_file_size"; + + // open/create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check file size (expect success and size == 0) + size = 1; + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, size); + + // write block + WritePatternChunk(handle, 0, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check size + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ(blk, size); + + storage_close_file(handle); + + // reopen normally + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check size again + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ(blk, size); + + // set file size to half + rc = storage_set_file_size(handle, blk/2, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check size again (should be half of original size) + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ(blk/2, size); + + // read data back + ReadPatternEOF(handle, 0, blk, blk/2); + ASSERT_FALSE(HasFatalFailure()); + + // set file size to 0 + rc = storage_set_file_size(handle, 0, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check size again (should be 0) + rc = storage_get_file_size(handle, &size); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0LL, size); + + // try to read again + ReadPatternEOF(handle, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +void StorageServiceTest::WriteReadAtOffsetHelper(file_handle_t handle, size_t blk, size_t cnt, bool complete) +{ + storage_off_t off1 = blk; + storage_off_t off2 = blk * (cnt-1); + + // write known pattern data at non-zero offset1 + WritePatternChunk(handle, off1, blk, complete); + ASSERT_FALSE(HasFatalFailure()); + + // write known pattern data at non-zero offset2 + WritePatternChunk(handle, off2, blk, complete); + ASSERT_FALSE(HasFatalFailure()); + + // read data back at offset1 + ReadPattern(handle, off1, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // read data back at offset2 + ReadPattern(handle, off2, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // read partially written data at end of file(expect to get data only, no padding) + ReadPatternEOF(handle, off2 + blk/2, blk, blk/2); + ASSERT_FALSE(HasFatalFailure()); + + // read data at offset 0 (expect success and zero data) + ReadChunk(handle, 0, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // read data from gap (expect success and zero data) + ReadChunk(handle, off1 + blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // read partially written data (start pointing within written data) + // (expect to get written data back and zeroes at the end) + ReadChunk(handle, off1 + blk/2, blk, 0, blk/2, blk/2); + ASSERT_FALSE(HasFatalFailure()); + + // read partially written data (start pointing withing unwritten data) + // expect to get zeroes at the beginning and proper data at the end + ReadChunk(handle, off1 - blk/2, blk, blk/2, blk/2, 0); + ASSERT_FALSE(HasFatalFailure()); +} + + +TEST_P(StorageServiceTest, WriteReadAtOffset) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t blk_cnt = 32; + const char *fname = "test_write_at_offset"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with zeroes + for (uint i = 0; i < blk_cnt; i++) { + WriteZeroChunk(handle, i * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + } + + WriteReadAtOffsetHelper(handle, blk, blk_cnt, true); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, WriteSparse) { + int rc; + file_handle_t handle; + const char *fname = "test_write_sparse"; + + // open/create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write value past en of file + uint32_t val = 0xDEADBEEF; + rc = storage_write(handle, 1, &val, sizeof(val), STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +// Persistent 32k + +TEST_P(StorageServiceTest, CreatePersistent32K) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t file_size = 32768; + const char *fname = "test_persistent_32K_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with pattern + WritePattern(handle, 0, file_size, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, ReadPersistent32k) { + int rc; + file_handle_t handle; + size_t exp_len = 32 * 1024; + const char *fname = "test_persistent_32K_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + ReadPatternEOF(handle, 0, 2048, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle, 0, 1024, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle, 0, 332, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, CleanUpPersistent32K) { + int rc; + const char *fname = "test_persistent_32K_file"; + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); +} + +// Persistent 1M +TEST_P(StorageServiceTest, CreatePersistent1M_4040) { + int rc; + file_handle_t handle; + size_t file_size = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with pattern + WritePattern(handle, 0, file_size, 4040, true); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, CreatePersistent1M_2032) { + int rc; + file_handle_t handle; + size_t file_size = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with pattern + WritePattern(handle, 0, file_size, 2032, true); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + + +TEST_P(StorageServiceTest, CreatePersistent1M_496) { + int rc; + file_handle_t handle; + size_t file_size = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with pattern + WritePattern(handle, 0, file_size, 496, true); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, CreatePersistent1M_240) { + int rc; + file_handle_t handle; + size_t file_size = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write a bunch of blocks filled with pattern + WritePattern(handle, 0, file_size, 240, true); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, ReadPersistent1M_4040) { + int rc; + file_handle_t handle; + size_t exp_len = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + ReadPatternEOF(handle, 0, 4040, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, ReadPersistent1M_2032) { + int rc; + file_handle_t handle; + size_t exp_len = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + ReadPatternEOF(handle, 0, 2032, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, ReadPersistent1M_496) { + int rc; + file_handle_t handle; + size_t exp_len = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + ReadPatternEOF(handle, 0, 496, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, ReadPersistent1M_240) { + int rc; + file_handle_t handle; + size_t exp_len = 1024 * 1024; + const char *fname = "test_persistent_1M_file"; + + // create/truncate file. + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + ReadPatternEOF(handle, 0, 240, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // close but do not delete file + storage_close_file(handle); +} + +TEST_P(StorageServiceTest, CleanUpPersistent1M) { + int rc; + const char *fname = "test_persistent_1M_file"; + rc = storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + rc = (rc == -ENOENT) ? 0 : rc; + ASSERT_EQ(0, rc); +} + +TEST_P(StorageServiceTest, WriteReadLong) { + int rc; + file_handle_t handle; + size_t wc = 10000; + const char *fname = "test_write_read_long"; + + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + test_buf_ = new uint32_t[wc]; + fill_pattern32(test_buf_, wc * sizeof(uint32_t), 0); + rc = storage_write(handle, 0, test_buf_, wc * sizeof(uint32_t), STORAGE_OP_COMPLETE); + ASSERT_EQ((int)(wc * sizeof(uint32_t)), rc); + + rc = storage_read(handle, 0, test_buf_, wc * sizeof(uint32_t)); + ASSERT_EQ((int)(wc * sizeof(uint32_t)), rc); + ASSERT_TRUE(check_pattern32(test_buf_, wc * sizeof(uint32_t), 0)); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +// Negative tests + +TEST_P(StorageServiceTest, OpenInvalidFileName) { + int rc; + file_handle_t handle; + const char *fname1 = ""; + const char *fname2 = "ffff$ffff"; + const char *fname3 = "ffff\\ffff"; + char max_name[STORAGE_MAX_NAME_LENGTH_BYTES+1]; + + rc = storage_open_file(session_, &handle, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + rc = storage_open_file(session_, &handle, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + rc = storage_open_file(session_, &handle, fname3, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + /* max name */ + memset(max_name, 'a', sizeof(max_name)); + max_name[sizeof(max_name)-1] = 0; + + rc = storage_open_file(session_, &handle, max_name, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + max_name[sizeof(max_name)-2] = 0; + rc = storage_open_file(session_, &handle, max_name, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + storage_close_file(handle); + storage_delete_file(session_, max_name, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, BadFileHnadle) { + int rc; + file_handle_t handle; + file_handle_t handle1; + const char *fname = "test_invalid_file_handle"; + + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + handle1 = handle + 1; + + // write to invalid file handle + uint32_t val = 0xDEDBEEF; + rc = storage_write(handle1, 0, &val, sizeof(val), STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + // read from invalid handle + rc = storage_read(handle1, 0, &val, sizeof(val)); + ASSERT_EQ(-EINVAL, rc); + + // set size + rc = storage_set_file_size(handle1, 0, STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + // get size + storage_off_t fsize = (storage_off_t)(-1); + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(-EINVAL, rc); + + // close (there is no way to check errors here) + storage_close_file(handle1); + + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, ClosedFileHnadle) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + const char *fname1 = "test_invalid_file_handle1"; + const char *fname2 = "test_invalid_file_handle2"; + + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + rc = storage_open_file(session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // close first file handle + storage_close_file(handle1); + + // write to invalid file handle + uint32_t val = 0xDEDBEEF; + rc = storage_write(handle1, 0, &val, sizeof(val), STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + // read from invalid handle + rc = storage_read(handle1, 0, &val, sizeof(val)); + ASSERT_EQ(-EINVAL, rc); + + // set size + rc = storage_set_file_size(handle1, 0, STORAGE_OP_COMPLETE); + ASSERT_EQ(-EINVAL, rc); + + // get size + storage_off_t fsize = (storage_off_t)(-1); + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(-EINVAL, rc); + + // close (there is no way to check errors here) + storage_close_file(handle1); + + // clean up + storage_close_file(handle2); + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE); +} + +// Transactions + +TEST_P(StorageServiceTest, TransactDiscardInactive) { + int rc; + + // discard current transaction (there should not be any) + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // try it again + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); +} + +TEST_P(StorageServiceTest, TransactCommitInactive) { + int rc; + + // try to commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // try it again + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); +} + +TEST_P(StorageServiceTest, TransactDiscardWrite) { + + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_write"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // write (without commit) + WritePattern(handle, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // cleanup + storage_close_file( handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactDiscardWriteAppend) { + + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_write_append"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data with commit + WritePattern(handle, 0, exp_len/2, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // write data without commit + WritePattern(handle, exp_len/2, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // check file size (should be exp_len) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // discard transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // check file size, it should be exp_len/2 + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + // check file data + ReadPatternEOF(handle, 0, blk, exp_len/2); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardWriteRead) { + + int rc; + file_handle_t handle; + size_t blk = 2048; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_write_read"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // Fill with zeroes (with commit) + for (uint i = 0; i < 32; i++) { + WriteZeroChunk(handle, i * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + } + + // check that test chunk is filled with zeroes + ReadChunk(handle, blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // write test pattern (without commit) + WritePattern(handle, blk, blk, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // read it back an check pattern + ReadChunk(handle, blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // read same chunk back (should be filled with zeros) + ReadChunk(handle, blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardWriteMany) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + size_t exp_len1 = 32 * 1024; + size_t exp_len2 = 31 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname1 = "test_transact_discard_write_file1"; + const char *fname2 = "test_transact_discard_write_file2"; + + // open create truncate (with commit) + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open create truncate (with commit) + rc = storage_open_file(session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // file1: fill file with pattern (without commit) + WritePattern(handle1, 0, exp_len1, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // file2: fill file with pattern (without commit) + WritePattern(handle2, 0, exp_len2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // check file size, it should be exp_len1 + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len1, fsize); + + // check file size, it should be exp_len2 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len2, fsize); + + // commit transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // check file size, it should be exp_len1 + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // check file size, it should be exp_len2 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // check data + ReadPatternEOF(handle1, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle2, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_close_file(handle2); + storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardTruncate) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_truncate"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // close file + storage_close_file(handle); + + // open truncate file (without commit) + rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // check file size (should be an oruginal size) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardSetSize) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_set_size"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // set file size to half of original (no commit) + rc = storage_set_file_size(handle, (storage_off_t)exp_len/2, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + // set file size to 1/3 of original (no commit) + rc = storage_set_file_size(handle, (storage_off_t)exp_len/3, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/3, fsize); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // check file size (should be an original size) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardDelete) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_delete"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close it + storage_close_file(handle); + + // delete file (without commit) + rc = storage_delete_file(session_, fname, 0); + ASSERT_EQ(0, rc); + + // try to open it (should fail) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // try to open it + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check file size (should be an original size) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactDiscardDelete2) { + int rc; + file_handle_t handle; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_discard_delete"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // delete file (without commit) + rc = storage_delete_file(session_, fname, 0); + ASSERT_EQ(0, rc); + storage_close_file(handle); + + // try to open it (should fail) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // try to open it + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check file size (should be an original size) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactDiscardCreate) { + int rc; + file_handle_t handle; + const char *fname = "test_transact_discard_create_excl"; + + // delete test file just in case + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + + // create file (without commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + 0); + ASSERT_EQ(0, rc); + + // abort current transaction + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactCommitWrites) { + + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_commit_writes"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open the same file in aux session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check file size, it should be 0 + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // write data in primary session (without commit) + WritePattern(handle, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // write more data in primary session (without commit) + WritePattern(handle, exp_len/2, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // check file size in aux session, it should still be 0 + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check file size of aux session, should fail + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(-EBUSY, rc); + + // abort transaction in aux session to recover + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // check file size in aux session, it should be exp_len + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // check file size in primary session, it should be exp_len + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // check data in primary session + ReadPatternEOF(handle, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // check data in aux session + ReadPatternEOF(handle_aux, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactCommitWrites2) { + + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_commit_writes2"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open the same file in separate session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // discard transaction in aux_session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // Fill with zeroes (with commit) + for (uint i = 0; i < 8; i++) { + WriteZeroChunk(handle, i * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + } + + // check that test chunks are filled with zeroes + ReadChunk(handle, blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadChunk(handle, 2 * blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // write test pattern (without commit) + WritePattern(handle, blk, blk, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // write test pattern (without commit) + WritePattern(handle, 2 * blk, blk, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // read it back and check pattern + ReadChunk(handle, blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadChunk(handle, 2 * blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // In aux session it still should be empty + ReadChunk(handle_aux, blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadChunk(handle_aux, 2 * blk, blk, blk, 0, 0); + ASSERT_FALSE(HasFatalFailure()); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // read same chunks back in primary session + ReadChunk(handle, blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadChunk(handle, 2 * blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // read same chunks back in aux session (should fail) + uint32_t val; + rc = storage_read(handle_aux, blk, &val, sizeof(val)); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_read(handle_aux, 2 * blk, &val, sizeof(val)); + ASSERT_EQ(-EBUSY, rc); + + // abort transaction in aux session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // read same chunk again in aux session + ReadChunk(handle_aux, blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + ReadChunk(handle_aux, 2 * blk, blk, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactCommitSetSize) { + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_commit_set_size"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open the same file in separate session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // same in aux session + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // set file size to half of original (no commit) + rc = storage_set_file_size(handle, (storage_off_t)exp_len/2, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // set file size to 1/3 of original (no commit) + rc = storage_set_file_size(handle, (storage_off_t)exp_len/3, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/3, fsize); + + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check file size (should be 1/3 of an original size) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/3, fsize); + + // check file size from aux session + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(-EBUSY, rc); + + // abort transaction in aux_session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // check again + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/3, fsize); + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactCommitDelete) { + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + const char *fname = "test_transact_commit_delete"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close it + storage_close_file(handle); + + // open the same file in separate session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + storage_close_file(handle_aux); + + // delete file (without commit) + rc = storage_delete_file(session_, fname, 0); + ASSERT_EQ(0, rc); + + // try to open it (should fail) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // open the same file in separate session (should be fine) + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + storage_close_file(handle_aux); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // try to open it in primary session (still fails) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // open the same file in aux session (should also fail) + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); +} + + +TEST_P(StorageServiceTest, TransactCommitTruncate) { + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_commit_truncate"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // close file + storage_close_file(handle); + + // check from different session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // open truncate file (without commit) + rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check file size (should be 0) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // check file size in aux session (should be -EBUSY) + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(-EBUSY, rc); + + // abort transaction in aux session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // check again + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactCommitCreate) { + int rc; + file_handle_t handle; + file_handle_t handle_aux; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_commit_create"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // delete test file just in case + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); + + // check from aux session + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // create file (without commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + 0); + ASSERT_EQ(0, rc); + + // check file size + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // close file + storage_close_file(handle); + + // check from aux session (should fail) + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check open from normal session + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // check open from aux session (should succeed) + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactCommitCreateMany) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + file_handle_t handle1_aux; + file_handle_t handle2_aux; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname1 = "test_transact_commit_create1"; + const char *fname2 = "test_transact_commit_create2"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // delete test file just in case + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE); + + // create file (without commit) + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + 0); + ASSERT_EQ(0, rc); + + // create file (without commit) + rc = storage_open_file(session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + 0); + ASSERT_EQ(0, rc); + + // check file sizes + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // close files + storage_close_file(handle1); + storage_close_file(handle2); + + // open files from aux session + rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // open from primary session + rc = storage_open_file(session_, &handle1, fname1, 0, 0); + ASSERT_EQ(0, rc); + + rc = storage_open_file(session_, &handle2, fname2, 0, 0); + ASSERT_EQ(0, rc); + + // open from aux session + rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0); + ASSERT_EQ(0, rc); + + rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0); + ASSERT_EQ(0, rc); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle1_aux); + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_close_file(handle2); + storage_close_file(handle2_aux); + storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactCommitWriteMany) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + file_handle_t handle1_aux; + file_handle_t handle2_aux; + size_t blk = 2048; + size_t exp_len1 = 32 * 1024; + size_t exp_len2 = 31 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname1 = "test_transact_commit_write_file1"; + const char *fname2 = "test_transact_commit_write_file2"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate (with commit) + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open create truncate (with commit) + rc = storage_open_file(session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // open same files from aux session + rc = storage_open_file(aux_session_, &handle1_aux, fname1, 0, 0); + ASSERT_EQ(0, rc); + + rc = storage_open_file(aux_session_, &handle2_aux, fname2, 0, 0); + ASSERT_EQ(0, rc); + + // file1: fill file with pattern (without commit) + WritePattern(handle1, 0, exp_len1, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // file2: fill file with pattern (without commit) + WritePattern(handle2, 0, exp_len2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // check file size, it should be exp_len1 + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len1, fsize); + + // check file size, it should be exp_len2 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len2, fsize); + + // check file sizes from aux session (should be 0) + rc = storage_get_file_size(handle1_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + rc = storage_get_file_size(handle2_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // commit transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check file size, it should be exp_len1 + rc = storage_get_file_size(handle1, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len1, fsize); + + // check file size, it should be exp_len2 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len2, fsize); + + // check from aux session (should be -EBUSY) + rc = storage_get_file_size(handle1_aux, &fsize); + ASSERT_EQ(-EBUSY, rc); + + // abort transaction in aux session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // and check again + rc = storage_get_file_size(handle1_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len1, fsize); + + rc = storage_get_file_size(handle2_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len2, fsize); + + // check data + ReadPatternEOF(handle1, 0, blk, exp_len1); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle2, 0, blk, exp_len2); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle1_aux, 0, blk, exp_len1); + ASSERT_FALSE(HasFatalFailure()); + + ReadPatternEOF(handle2_aux, 0, blk, exp_len2); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle1_aux); + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_close_file(handle2); + storage_close_file(handle2_aux); + storage_delete_file(session_, fname2, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactCommitDeleteCreate) { + int rc; + file_handle_t handle; + file_handle_t handle_aux; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_delete_create"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, exp_len, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close it + storage_close_file(handle); + + // delete file (without commit) + rc = storage_delete_file(session_, fname, 0); + ASSERT_EQ(0, rc); + + // try to open it (should fail) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(-ENOENT, rc); + + // try to open it in aux session (should succeed) + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // create file with the same name (no commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_CREATE_EXCLUSIVE, + 0); + ASSERT_EQ(0, rc); + + // write half of data (with commit) + WritePattern(handle, 0, exp_len/2, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // check file size (should be half) + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + // commit transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check data from primary session + ReadPatternEOF(handle, 0, blk, exp_len/2); + ASSERT_FALSE(HasFatalFailure()); + + // check from aux session (should fail) + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(-EINVAL, rc); + + // abort trunsaction in aux session + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // and try again (should still fail) + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(-EINVAL, rc); + + // close file and reopen it again + storage_close_file(handle_aux); + rc = storage_open_file(aux_session_, &handle_aux, fname, 0, 0); + ASSERT_EQ(0, rc); + + // try it again (should succeed) + rc = storage_get_file_size(handle_aux, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + // check data + ReadPatternEOF(handle_aux, 0, blk, exp_len/2); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_close_file(handle_aux); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, TransactRewriteExistingTruncate) { + int rc; + file_handle_t handle; + size_t blk = 2048; + const char *fname = "test_transact_rewrite_existing_truncate"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // close it + storage_close_file(handle); + + // up + for (uint i = 1; i < 32; i++) { + // open truncate (no commit) + rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, i * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close + storage_close_file(handle); + } + + // down + for (uint i = 1; i < 32; i++) { + // open truncate (no commit) + rc = storage_open_file(session_, &handle, fname, STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, (32 - i) * blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // close + storage_close_file(handle); + } + + // cleanup + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactRewriteExistingSetSize) { + int rc; + file_handle_t handle; + size_t blk = 2048; + const char *fname = "test_transact_rewrite_existing_set_size"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // close it + storage_close_file(handle); + + // up + for (uint i = 1; i < 32; i++) { + // open truncate (no commit) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, i * blk, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // update size (with commit) + rc = storage_set_file_size(handle, i * blk, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // close + storage_close_file(handle); + } + + // down + for (uint i = 1; i < 32; i++) { + // open trancate (no commit) + rc = storage_open_file(session_, &handle, fname, 0, 0); + ASSERT_EQ(0, rc); + + // write data (with commit) + WritePattern(handle, 0, (32 - i) * blk, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // update size (with commit) + rc = storage_set_file_size(handle, (32 - i) * blk, STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // close + storage_close_file(handle); + } + + // cleanup + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, TransactResumeAfterNonFatalError) { + + int rc; + file_handle_t handle; + file_handle_t handle1; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_resume_writes"; + + // open create truncate file (with commit) + rc = storage_open_file(session_, &handle, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // write (without commit) + WritePattern(handle, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // issue some commands that should fail with non-fatal errors + + // write past end of file + uint32_t val = 0xDEDBEEF; + rc = storage_write(handle, exp_len/2 + 1, &val, sizeof(val), 0); + ASSERT_EQ(-EINVAL, rc); + + // read past end of file + rc = storage_read(handle, exp_len/2 + 1, &val, sizeof(val)); + ASSERT_EQ(-EINVAL, rc); + + // try to extend file past end of file + rc = storage_set_file_size(handle, exp_len/2 + 1, 0); + ASSERT_EQ(-EINVAL, rc); + + // open non existing file + rc = storage_open_file(session_, &handle1, "foo", + STORAGE_FILE_OPEN_TRUNCATE, STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); + + // delete non-existing file + rc = storage_delete_file(session_, "foo", STORAGE_OP_COMPLETE); + ASSERT_EQ(-ENOENT, rc); + + // then resume writinga (without commit) + WritePattern(handle, exp_len/2, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // commit current transaction + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // check file size, it should be exp_len + rc = storage_get_file_size(handle, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // check data + ReadPatternEOF(handle, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle); + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +// Transaction Collisions + +TEST_P(StorageServiceTest, Transact2_WriteNC) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + const char *fname1 = "test_transact_f1"; + const char *fname2 = "test_transact_f2"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + rc = storage_open_file(aux_session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // session 1 + WritePattern(handle1, 0, blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // read it back + ReadPatternEOF(handle1, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // session 2 + WritePattern(handle2, 0, blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // read it back + ReadPatternEOF(handle2, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname1, STORAGE_OP_COMPLETE); + storage_delete_file(aux_session_, fname2, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, Transact2_DeleteNC) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + const char *fname1 = "test_transact_delete_f1"; + const char *fname2 = "test_transact_delete_f2"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + rc = storage_open_file(session_, &handle1, fname1, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + rc = storage_open_file(aux_session_, &handle2, fname2, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // session 1 + WritePattern(handle1, 0, blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // read it back + ReadPatternEOF(handle1, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // session 2 + WritePattern(handle2, 0, blk, blk, true); + ASSERT_FALSE(HasFatalFailure()); + + // read it back + ReadPatternEOF(handle2, 0, blk, blk); + ASSERT_FALSE(HasFatalFailure()); + + // close files and delete them + storage_close_file(handle1); + storage_delete_file(session_, fname1, 0); + + storage_close_file(handle2); + storage_delete_file(aux_session_, fname2, 0); + + // commit + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + rc = storage_end_transaction(aux_session_, true); + ASSERT_EQ(0, rc); +} + + +TEST_P(StorageServiceTest, Transact2_Write_Read) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_writeRead"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // S1: open create truncate file + rc = storage_open_file(session_, &handle1, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S2: open the same file + rc = storage_open_file(aux_session_, &handle2, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S1: write (no commit) + WritePattern(handle1, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S1: read it back + ReadPatternEOF(handle1, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // S2: check file size, it should be 0 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // S2: read it back (should no data) + ReadPatternEOF(handle2, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // S1: commit + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // S2: check file size, it should fail + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(-EBUSY, rc); + + // S2: abort transaction + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // S2: check file size again, it should be exp_len + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // S2: read it again (should be exp_len) + ReadPatternEOF(handle2, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, Transact2_Write_Write_Commit_Commit) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + file_handle_t handle3; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_write_write_commit_commit"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // S1: open create truncate file + rc = storage_open_file(session_, &handle1, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S2: open the same file + rc = storage_open_file(aux_session_, &handle2, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S1: write (no commit) + WritePattern(handle1, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S2: write (no commit) + WritePattern(handle2, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S1: commit + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // S2: read/write/get/set size/delete (all should fail) + uint32_t val = 0; + rc = storage_read(handle2, 0, &val, sizeof(val)); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_write(handle2, 0, &val, sizeof(val), 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_set_file_size(handle2, fsize, 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_delete_file(aux_session_, fname, 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_open_file(aux_session_, &handle3, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(-EBUSY, rc); + + // S2: commit (should fail, and failed state should be cleared) + rc = storage_end_transaction(aux_session_, true); + ASSERT_EQ(-EBUSY, rc); + + // S2: check file size, it should be exp_len + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // S2: read it again (should be exp_len) + ReadPatternEOF(handle2, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, Transact2_Write_Write_Commit_Discard) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + file_handle_t handle3; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_write_write_commit_discard"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // S1: open create truncate file + rc = storage_open_file(session_, &handle1, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S2: open the same file + rc = storage_open_file(aux_session_, &handle2, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S1: write (no commit) + WritePattern(handle1, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S2: write (no commit) + WritePattern(handle2, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S1: commit + rc = storage_end_transaction(session_, true); + ASSERT_EQ(0, rc); + + // S2: read/write/get/set size/delete (all should fail) + uint32_t val = 0; + rc = storage_read(handle2, 0, &val, sizeof(val)); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_write(handle2, 0, &val, sizeof(val), 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_set_file_size(handle2, fsize, 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_delete_file(aux_session_, fname, 0); + ASSERT_EQ(-EBUSY, rc); + + rc = storage_open_file(aux_session_, &handle3, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, 0); + ASSERT_EQ(-EBUSY, rc); + + // S2: discard (should fail, and failed state should be cleared) + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // S2: check file size, it should be exp_len + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len, fsize); + + // S2: read it again (should be exp_len) + ReadPatternEOF(handle2, 0, blk, exp_len); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + +TEST_P(StorageServiceTest, Transact2_Write_Write_Discard_Commit) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_write_write_discard_commit"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // S1: open create truncate file + rc = storage_open_file(session_, &handle1, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S2: open the same file + rc = storage_open_file(aux_session_, &handle2, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S1: write (no commit) + WritePattern(handle1, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S2: write (no commit) + WritePattern(handle2, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S1: discard + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // S2: commit (should succeed) + rc = storage_end_transaction(aux_session_, true); + ASSERT_EQ(0, rc); + + // S2: check file size, it should be exp_len + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)exp_len/2, fsize); + + // S2: read it again (should be exp_len) + ReadPatternEOF(handle2, 0, blk, exp_len/2); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} + + +TEST_P(StorageServiceTest, Transact2_Write_Write_Discard_Discard) { + int rc; + file_handle_t handle1; + file_handle_t handle2; + size_t blk = 2048; + size_t exp_len = 32 * 1024; + storage_off_t fsize = (storage_off_t)(-1); + const char *fname = "test_transact_write_write_discard_Discard"; + + // open second session + rc = storage_open_session(TRUSTY_DEVICE_NAME, &aux_session_, port_); + ASSERT_EQ(0, rc); + + // S1: open create truncate file + rc = storage_open_file(session_, &handle1, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S2: open the same file + rc = storage_open_file(aux_session_, &handle2, fname, + STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE, + STORAGE_OP_COMPLETE); + ASSERT_EQ(0, rc); + + // S1: write (no commit) + WritePattern(handle1, 0, exp_len, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S2: write (no commit) + WritePattern(handle2, 0, exp_len/2, blk, false); + ASSERT_FALSE(HasFatalFailure()); + + // S1: discard + rc = storage_end_transaction(session_, false); + ASSERT_EQ(0, rc); + + // S2: discard + rc = storage_end_transaction(aux_session_, false); + ASSERT_EQ(0, rc); + + // S2: check file size, it should be 0 + rc = storage_get_file_size(handle2, &fsize); + ASSERT_EQ(0, rc); + ASSERT_EQ((storage_off_t)0, fsize); + + // S2: read it again (should be 0) + ReadPatternEOF(handle2, 0, blk, 0); + ASSERT_FALSE(HasFatalFailure()); + + // cleanup + storage_close_file(handle1); + storage_close_file(handle2); + + storage_delete_file(session_, fname, STORAGE_OP_COMPLETE); +} +