mirror of https://github.com/ARMmbed/mbed-os.git
638 lines
17 KiB
C++
638 lines
17 KiB
C++
/* mbed Microcontroller Library
|
|
* Copyright (c) 2018 ARM Limited
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*
|
|
* 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 "filesystemstore/FileSystemStore.h"
|
|
#include "kv_config/kv_config.h"
|
|
#include "filesystem/Dir.h"
|
|
#include "filesystem/File.h"
|
|
#include "blockdevice/BlockDevice.h"
|
|
#include "mbed_error.h"
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "mbed-trace/mbed_trace.h"
|
|
#define TRACE_GROUP "FSST"
|
|
|
|
#define FSST_REVISION 1
|
|
#define FSST_MAGIC 0x46535354 // "FSST" hex 'magic' signature
|
|
|
|
#define FSST_DEFAULT_FOLDER_PATH "kvstore" //default FileSystemStore folder path on fs
|
|
|
|
// Only write once flag is supported, other two are kept in storage but ignored
|
|
static const uint32_t supported_flags = mbed::KVStore::WRITE_ONCE_FLAG | mbed::KVStore::REQUIRE_CONFIDENTIALITY_FLAG |
|
|
mbed::KVStore::REQUIRE_REPLAY_PROTECTION_FLAG;
|
|
|
|
using namespace mbed;
|
|
|
|
namespace {
|
|
|
|
// incremental set handle
|
|
typedef struct {
|
|
char *key;
|
|
uint32_t create_flags;
|
|
size_t data_size;
|
|
File *file_handle;
|
|
} inc_set_handle_t;
|
|
|
|
// iterator handle
|
|
typedef struct {
|
|
void *dir_handle;
|
|
char *prefix;
|
|
} key_iterator_handle_t;
|
|
|
|
} // anonymous namespace
|
|
|
|
// Local Functions
|
|
static char *string_ndup(const char *src, size_t size);
|
|
|
|
|
|
// Class Functions
|
|
FileSystemStore::FileSystemStore(FileSystem *fs) : _fs(fs),
|
|
_is_initialized(false), _cfg_fs_path(NULL), _cfg_fs_path_size(0),
|
|
_full_path_key(NULL), _cur_inc_data_size(0), _cur_inc_set_handle(NULL)
|
|
{
|
|
|
|
}
|
|
|
|
int FileSystemStore::init()
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
|
|
_mutex.lock();
|
|
const char *temp_path = get_filesystemstore_folder_path();
|
|
if (temp_path == NULL) {
|
|
_cfg_fs_path_size = strlen(FSST_DEFAULT_FOLDER_PATH);
|
|
_cfg_fs_path = string_ndup(FSST_DEFAULT_FOLDER_PATH, _cfg_fs_path_size);
|
|
} else {
|
|
_cfg_fs_path_size = strlen(temp_path);
|
|
_cfg_fs_path = string_ndup(temp_path, _cfg_fs_path_size);
|
|
}
|
|
|
|
_full_path_key = new char[_cfg_fs_path_size + KVStore::MAX_KEY_SIZE + 1];
|
|
memset(_full_path_key, 0, (_cfg_fs_path_size + KVStore::MAX_KEY_SIZE + 1));
|
|
strncpy(_full_path_key, _cfg_fs_path, _cfg_fs_path_size);
|
|
_full_path_key[_cfg_fs_path_size] = '/';
|
|
_cur_inc_data_size = 0;
|
|
_cur_inc_set_handle = NULL;
|
|
Dir kv_dir;
|
|
|
|
if (kv_dir.open(_fs, _cfg_fs_path) != 0) {
|
|
tr_info("KV Dir: %s, doesnt exist - creating new.. ", _cfg_fs_path); //TBD verify ERRNO NOEXIST
|
|
if (_fs->mkdir(_cfg_fs_path,/* which flags ? */0777) != 0) {
|
|
tr_error("KV Dir: %s, mkdir failed.. ", _cfg_fs_path); //TBD verify ERRNO NOEXIST
|
|
status = MBED_ERROR_FAILED_OPERATION;
|
|
goto exit_point;
|
|
}
|
|
} else {
|
|
tr_info("KV Dir: %s, exists(verified) - now closing it", _cfg_fs_path);
|
|
if (kv_dir.close() != 0) {
|
|
tr_error("KV Dir: %s, dir_close failed", _cfg_fs_path); //TBD verify ERRNO NOEXIST
|
|
}
|
|
}
|
|
|
|
_is_initialized = true;
|
|
exit_point:
|
|
|
|
_mutex.unlock();
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
int FileSystemStore::deinit()
|
|
{
|
|
_mutex.lock();
|
|
_is_initialized = false;
|
|
delete[] _cfg_fs_path;
|
|
delete[] _full_path_key;
|
|
_mutex.unlock();
|
|
return MBED_SUCCESS;
|
|
|
|
}
|
|
|
|
int FileSystemStore::reset()
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
Dir kv_dir;
|
|
struct dirent dir_ent;
|
|
|
|
_mutex.lock();
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
kv_dir.open(_fs, _cfg_fs_path);
|
|
|
|
while (kv_dir.read(&dir_ent) != 0) {
|
|
if (dir_ent.d_type != DT_REG) {
|
|
continue;
|
|
}
|
|
// Build File's full path name and delete it (even if write-onced)
|
|
_build_full_path_key(dir_ent.d_name);
|
|
_fs->remove(_full_path_key);
|
|
}
|
|
|
|
kv_dir.close();
|
|
|
|
exit_point:
|
|
_mutex.unlock();
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::set(const char *key, const void *buffer, size_t size, uint32_t create_flags)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
set_handle_t handle;
|
|
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
if ((!is_valid_key(key)) || ((buffer == NULL) && (size > 0))) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
status = set_start(&handle, key, size, create_flags);
|
|
if (status != MBED_SUCCESS) {
|
|
tr_error("FSST Set set_start Failed: %d", status);
|
|
goto exit_point;
|
|
}
|
|
|
|
status = set_add_data(handle, buffer, size);
|
|
if (status != MBED_SUCCESS) {
|
|
tr_error("FSST Set set_add_data Failed: %d", status);
|
|
set_finalize(handle);
|
|
goto exit_point;
|
|
}
|
|
|
|
status = set_finalize(handle);
|
|
if (status != MBED_SUCCESS) {
|
|
tr_error("FSST Set set_finalize Failed: %d", status);
|
|
goto exit_point;
|
|
}
|
|
|
|
exit_point:
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::get(const char *key, void *buffer, size_t buffer_size, size_t *actual_size, size_t offset)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
|
|
File kv_file;
|
|
size_t kv_file_size = 0;
|
|
size_t value_actual_size = 0;
|
|
|
|
_mutex.lock();
|
|
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
key_metadata_t key_metadata;
|
|
|
|
if ((status = _verify_key_file(key, &key_metadata, &kv_file)) != MBED_SUCCESS) {
|
|
tr_debug("File Verification failed, status: %d", status);
|
|
goto exit_point;
|
|
}
|
|
|
|
kv_file_size = kv_file.size() - key_metadata.metadata_size;
|
|
// Actual size is the minimum of buffer_size and remainder of data in file (file's data size - offset)
|
|
value_actual_size = buffer_size;
|
|
if (offset > kv_file_size) {
|
|
status = MBED_ERROR_INVALID_SIZE;
|
|
goto exit_point;
|
|
} else if ((kv_file_size - offset) < buffer_size) {
|
|
value_actual_size = kv_file_size - offset;
|
|
}
|
|
|
|
if ((buffer == NULL) && (value_actual_size > 0)) {
|
|
status = MBED_ERROR_INVALID_DATA_DETECTED;
|
|
goto exit_point;
|
|
}
|
|
|
|
if (actual_size != NULL) {
|
|
*actual_size = value_actual_size;
|
|
}
|
|
|
|
kv_file.seek(key_metadata.metadata_size + offset, SEEK_SET);
|
|
// Read remainder of data
|
|
kv_file.read(buffer, value_actual_size);
|
|
|
|
exit_point:
|
|
if ((status == MBED_SUCCESS) ||
|
|
(status == MBED_ERROR_INVALID_DATA_DETECTED)) {
|
|
kv_file.close();
|
|
}
|
|
_mutex.unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::get_info(const char *key, info_t *info)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
File kv_file;
|
|
|
|
_mutex.lock();
|
|
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
key_metadata_t key_metadata;
|
|
|
|
if ((status = _verify_key_file(key, &key_metadata, &kv_file)) != MBED_SUCCESS) {
|
|
tr_debug("File Verification failed, status: %d", status);
|
|
goto exit_point;
|
|
}
|
|
|
|
if (info != NULL) {
|
|
info->size = kv_file.size() - key_metadata.metadata_size;
|
|
info->flags = key_metadata.user_flags;
|
|
}
|
|
|
|
exit_point:
|
|
if ((status == MBED_SUCCESS) ||
|
|
(status == MBED_ERROR_INVALID_DATA_DETECTED)) {
|
|
kv_file.close();
|
|
}
|
|
_mutex.unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::remove(const char *key)
|
|
{
|
|
File kv_file;
|
|
key_metadata_t key_metadata;
|
|
|
|
_mutex.lock();
|
|
|
|
int status = MBED_SUCCESS;
|
|
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
/* If File Exists and is Valid, then check its Write Once Flag to verify its disabled before removing */
|
|
/* If File exists and is not valid, or is Valid and not Write-Onced then remove it */
|
|
if ((status = _verify_key_file(key, &key_metadata, &kv_file)) == MBED_SUCCESS) {
|
|
if (key_metadata.user_flags & KVStore::WRITE_ONCE_FLAG) {
|
|
kv_file.close();
|
|
tr_error("File: %s, Exists but write protected", _full_path_key);
|
|
status = MBED_ERROR_WRITE_PROTECTED;
|
|
goto exit_point;
|
|
}
|
|
} else if ((status == MBED_ERROR_ITEM_NOT_FOUND) ||
|
|
(status == MBED_ERROR_INVALID_ARGUMENT)) {
|
|
goto exit_point;
|
|
}
|
|
kv_file.close();
|
|
|
|
if (0 != _fs->remove(_full_path_key)) {
|
|
status = MBED_ERROR_FAILED_OPERATION;
|
|
}
|
|
|
|
exit_point:
|
|
_mutex.unlock();
|
|
return status;
|
|
}
|
|
|
|
// Incremental set API
|
|
int FileSystemStore::set_start(set_handle_t *handle, const char *key, size_t final_data_size, uint32_t create_flags)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
inc_set_handle_t *set_handle = NULL;
|
|
File *kv_file;
|
|
key_metadata_t key_metadata;
|
|
int key_len = 0;
|
|
|
|
if (create_flags & ~supported_flags) {
|
|
return MBED_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
// Only a single key file can be incrementaly editted at a time
|
|
_mutex.lock();
|
|
|
|
kv_file = new File;
|
|
|
|
if (handle == NULL) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
/* If File Exists and is Valid, then check its Write Once Flag to verify its disabled before setting */
|
|
/* If File exists and is not valid, or is Valid and not Write-Onced then erase it */
|
|
status = _verify_key_file(key, &key_metadata, kv_file);
|
|
|
|
if (status == MBED_ERROR_INVALID_ARGUMENT) {
|
|
tr_error("File Verification failed, status: %d", status);
|
|
goto exit_point;
|
|
}
|
|
|
|
if (status == MBED_SUCCESS) {
|
|
if (key_metadata.user_flags & KVStore::WRITE_ONCE_FLAG) {
|
|
kv_file->close();
|
|
status = MBED_ERROR_WRITE_PROTECTED;
|
|
goto exit_point;
|
|
}
|
|
}
|
|
|
|
/* For Success (not write_once) and for corrupted data close file before recreating it as a new file */
|
|
if (status != MBED_ERROR_ITEM_NOT_FOUND) {
|
|
kv_file->close();
|
|
}
|
|
|
|
if ((status = kv_file->open(_fs, _full_path_key, O_WRONLY | O_CREAT | O_TRUNC)) != MBED_SUCCESS) {
|
|
tr_info("set_start failed to open: %s, for writing, err: %d", _full_path_key, status);
|
|
status = MBED_ERROR_FAILED_OPERATION ;
|
|
goto exit_point;
|
|
}
|
|
_cur_inc_data_size = 0;
|
|
|
|
set_handle = new inc_set_handle_t;
|
|
set_handle->create_flags = create_flags;
|
|
set_handle->data_size = final_data_size;
|
|
set_handle->file_handle = kv_file;
|
|
key_len = strlen(key);
|
|
set_handle->key = string_ndup(key, key_len);
|
|
*handle = (set_handle_t)set_handle;
|
|
_cur_inc_set_handle = *handle;
|
|
|
|
key_metadata.magic = FSST_MAGIC;
|
|
key_metadata.metadata_size = sizeof(key_metadata_t);
|
|
key_metadata.revision = FSST_REVISION;
|
|
key_metadata.user_flags = create_flags;
|
|
kv_file->write(&key_metadata, sizeof(key_metadata_t));
|
|
|
|
exit_point:
|
|
if (status != MBED_SUCCESS) {
|
|
delete kv_file;
|
|
_mutex.unlock();
|
|
}
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::set_add_data(set_handle_t handle, const void *value_data, size_t data_size)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
size_t added_data = 0;
|
|
inc_set_handle_t *set_handle = (inc_set_handle_t *)handle;
|
|
File *kv_file;
|
|
|
|
if (((value_data == NULL) && (data_size > 0)) || (handle == NULL) || (handle != _cur_inc_set_handle)) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
// Single key incrementally edited, can be edited from multiple threads - lock to protect
|
|
_inc_data_add_mutex.lock();
|
|
if ((_cur_inc_data_size + data_size) > set_handle->data_size) {
|
|
tr_warning("Added Data(%d) will exceed set_start final size(%d) - not adding data to file: %s",
|
|
_cur_inc_data_size + data_size, set_handle->data_size, _full_path_key);
|
|
status = MBED_ERROR_INVALID_SIZE;
|
|
goto exit_point;
|
|
}
|
|
|
|
kv_file = set_handle->file_handle;
|
|
|
|
added_data = kv_file->write(value_data, data_size);
|
|
if (added_data != data_size) {
|
|
status = MBED_ERROR_FAILED_OPERATION ;
|
|
}
|
|
_cur_inc_data_size += added_data;
|
|
|
|
exit_point:
|
|
if (status != MBED_ERROR_INVALID_ARGUMENT) {
|
|
_inc_data_add_mutex.unlock();
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::set_finalize(set_handle_t handle)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
inc_set_handle_t *set_handle = NULL;
|
|
|
|
if ((handle == NULL) || (handle != _cur_inc_set_handle)) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
set_handle = (inc_set_handle_t *)handle;
|
|
|
|
if (set_handle->key == NULL) {
|
|
status = MBED_ERROR_INVALID_DATA_DETECTED;
|
|
} else {
|
|
if (_cur_inc_data_size != set_handle->data_size) {
|
|
tr_error("Accumulated Data (%d) size doesn't match set_start final size (%d) - file: %s", _cur_inc_data_size,
|
|
set_handle->data_size, _full_path_key);
|
|
status = MBED_ERROR_INVALID_SIZE;
|
|
_fs->remove(_full_path_key);
|
|
}
|
|
delete[] set_handle->key;
|
|
}
|
|
|
|
set_handle->file_handle->close();
|
|
delete set_handle->file_handle;
|
|
delete set_handle;
|
|
_cur_inc_data_size = 0;
|
|
_cur_inc_set_handle = NULL;
|
|
|
|
exit_point:
|
|
if (status != MBED_ERROR_INVALID_ARGUMENT) {
|
|
_mutex.unlock();
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::iterator_open(iterator_t *it, const char *prefix)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
Dir *kv_dir = NULL;
|
|
key_iterator_handle_t *key_it = NULL;
|
|
|
|
if (it == NULL) {
|
|
return MBED_ERROR_INVALID_ARGUMENT;
|
|
}
|
|
|
|
_mutex.lock();
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
key_it = new key_iterator_handle_t;
|
|
key_it->dir_handle = NULL;
|
|
key_it->prefix = NULL;
|
|
if (prefix != NULL) {
|
|
key_it->prefix = string_ndup(prefix, KVStore::MAX_KEY_SIZE);
|
|
}
|
|
|
|
kv_dir = new Dir;
|
|
if (kv_dir->open(_fs, _cfg_fs_path) != 0) {
|
|
tr_error("KV Dir: %s, doesnt exist", _cfg_fs_path); //TBD verify ERRNO NOEXIST
|
|
delete kv_dir;
|
|
if (key_it->prefix != NULL) {
|
|
delete[] key_it->prefix;
|
|
}
|
|
delete key_it;
|
|
status = MBED_ERROR_ITEM_NOT_FOUND;
|
|
goto exit_point;
|
|
}
|
|
|
|
key_it->dir_handle = kv_dir;
|
|
|
|
*it = (iterator_t)key_it;
|
|
|
|
exit_point:
|
|
_mutex.unlock();
|
|
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::iterator_next(iterator_t it, char *key, size_t key_size)
|
|
{
|
|
Dir *kv_dir;
|
|
struct dirent kv_dir_ent;
|
|
int status = MBED_ERROR_ITEM_NOT_FOUND;
|
|
key_iterator_handle_t *key_it;
|
|
size_t key_name_size = KVStore::MAX_KEY_SIZE;
|
|
if (key_size < key_name_size) {
|
|
key_name_size = key_size;
|
|
}
|
|
|
|
_mutex.lock();
|
|
if (false == _is_initialized) {
|
|
status = MBED_ERROR_NOT_READY;
|
|
goto exit_point;
|
|
}
|
|
|
|
key_it = (key_iterator_handle_t *)it;
|
|
|
|
if ((key_it->prefix != NULL) && (key_name_size < strlen(key_it->prefix))) {
|
|
status = MBED_ERROR_INVALID_SIZE;
|
|
goto exit_point;
|
|
}
|
|
|
|
kv_dir = (Dir *)key_it->dir_handle;
|
|
|
|
while (kv_dir->read(&kv_dir_ent) != 0) {
|
|
if (kv_dir_ent.d_type != DT_REG) {
|
|
continue;
|
|
}
|
|
|
|
if ((key_it->prefix == NULL) ||
|
|
(strncmp(kv_dir_ent.d_name, key_it->prefix, strlen(key_it->prefix)) == 0)) {
|
|
if (key_name_size < strlen(kv_dir_ent.d_name)) {
|
|
status = MBED_ERROR_INVALID_SIZE;
|
|
break;
|
|
}
|
|
strncpy(key, kv_dir_ent.d_name, key_name_size);
|
|
key[key_name_size - 1] = '\0';
|
|
status = MBED_SUCCESS;
|
|
break;
|
|
}
|
|
}
|
|
|
|
exit_point:
|
|
_mutex.unlock();
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::iterator_close(iterator_t it)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
key_iterator_handle_t *key_it = (key_iterator_handle_t *)it;
|
|
|
|
_mutex.lock();
|
|
if (key_it == NULL) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
if (key_it->prefix != NULL) {
|
|
delete[] key_it->prefix;
|
|
}
|
|
|
|
if (key_it->dir_handle != NULL) {
|
|
((Dir *)(key_it->dir_handle))->close();
|
|
delete ((Dir *)(key_it->dir_handle));
|
|
}
|
|
delete key_it;
|
|
|
|
exit_point:
|
|
_mutex.unlock();
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::_verify_key_file(const char *key, key_metadata_t *key_metadata, File *kv_file)
|
|
{
|
|
int status = MBED_SUCCESS;
|
|
|
|
if (!is_valid_key(key)) {
|
|
status = MBED_ERROR_INVALID_ARGUMENT;
|
|
goto exit_point;
|
|
}
|
|
|
|
_build_full_path_key(key);
|
|
|
|
if (0 != kv_file->open(_fs, _full_path_key, O_RDONLY)) {
|
|
status = MBED_ERROR_ITEM_NOT_FOUND;
|
|
goto exit_point;
|
|
}
|
|
|
|
//Read Metadata
|
|
kv_file->read(key_metadata, sizeof(key_metadata_t));
|
|
|
|
if ((key_metadata->magic != FSST_MAGIC) ||
|
|
(key_metadata->revision > FSST_REVISION)) {
|
|
status = MBED_ERROR_INVALID_DATA_DETECTED;
|
|
goto exit_point;
|
|
}
|
|
|
|
exit_point:
|
|
return status;
|
|
}
|
|
|
|
int FileSystemStore::_build_full_path_key(const char *key_src)
|
|
{
|
|
strncpy(&_full_path_key[_cfg_fs_path_size + 1/* for path's \ */], key_src, KVStore::MAX_KEY_SIZE);
|
|
_full_path_key[(_cfg_fs_path_size + KVStore::MAX_KEY_SIZE)] = '\0';
|
|
return 0;
|
|
}
|
|
|
|
// Local Functions
|
|
static char *string_ndup(const char *src, size_t size)
|
|
{
|
|
char *string_copy = new char[size + 1];
|
|
strncpy(string_copy, src, size);
|
|
string_copy[size] = '\0';
|
|
return string_copy;
|
|
}
|