2861 lines
69 KiB
C
2861 lines
69 KiB
C
/*
|
||
* Copyright(c) 2012-2019 Intel Corporation
|
||
* SPDX-License-Identifier: BSD-3-Clause-Clear
|
||
*/
|
||
|
||
#include <stdio.h>
|
||
#include <errno.h>
|
||
#include <assert.h>
|
||
#include <fcntl.h>
|
||
#include <stdint.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
#include <inttypes.h>
|
||
#include <limits.h>
|
||
#include <fstab.h>
|
||
#include <linux/fs.h>
|
||
#include <linux/types.h>
|
||
#include <linux/major.h>
|
||
#include <mntent.h>
|
||
#include <sys/mman.h>
|
||
#include <sys/stat.h>
|
||
#include <sys/ioctl.h>
|
||
#include <sys/types.h>
|
||
#include <time.h>
|
||
#include <pthread.h>
|
||
#include <dirent.h>
|
||
#include <stdbool.h>
|
||
#include "cas_lib.h"
|
||
#include "extended_err_msg.h"
|
||
#include "cas_lib_utils.h"
|
||
#include "csvparse.h"
|
||
#include "statistics_view.h"
|
||
#include "safeclib/safe_mem_lib.h"
|
||
#include "safeclib/safe_str_lib.h"
|
||
#include "safeclib/safe_lib.h"
|
||
#include <cas_ioctl_codes.h>
|
||
#include <cas_version.h>
|
||
#include "psort.h"
|
||
#define PRINT_STAT(x) header->cmd_input.cache_stats.x
|
||
|
||
#define CORE_ADD_MAX_TIMEOUT 30
|
||
|
||
#define CHECK_IF_CACHE_IS_MOUNTED -1
|
||
|
||
/**
|
||
* @brief Routine verifies if filesystem is currently mounted for given cache/core
|
||
*
|
||
* If FAILURE is returned, reason for failure is printed onto
|
||
* standard error.
|
||
* @param cache_id cache id of filesystem (to verify if it is mounted)
|
||
* @param core_id core id of filesystem (to verify if it is mounted); if this
|
||
* parameter is set to negative value, it is only checked if any core belonging
|
||
* to given cache is mounted;
|
||
* @return SUCCESS if is not mounted; FAILURE if filesystem is mounted
|
||
*/
|
||
int check_if_mounted(int cache_id, int core_id);
|
||
|
||
/* KCAS_IOCTL_CACHE_CHECK_DEVICE wrapper */
|
||
int _check_cache_device(const char *device_path,
|
||
struct kcas_cache_check_device *cmd_info);
|
||
|
||
static const char *cache_states_name[ocf_cache_state_max + 1] = {
|
||
[ocf_cache_state_running] = "Running",
|
||
[ocf_cache_state_stopping] = "Stopping",
|
||
[ocf_cache_state_initializing] = "Initializing",
|
||
[ocf_cache_state_incomplete] = "Incomplete",
|
||
[ocf_cache_state_max] = "Unknown",
|
||
};
|
||
|
||
static const char *core_states_name[] = {
|
||
[ocf_core_state_active] = "Active",
|
||
[ocf_core_state_inactive] = "Inactive",
|
||
};
|
||
|
||
#define NOT_RUNNING_STATE "Not running"
|
||
|
||
#define CACHE_STATE_LENGHT 20
|
||
|
||
#define CAS_LOG_FILE "/var/log/opencas.log"
|
||
#define CAS_LOG_LEVEL LOG_INFO
|
||
|
||
int vcaslog(int log_level, const char *template, va_list args)
|
||
{
|
||
FILE *log;
|
||
time_t t;
|
||
struct tm *tm;
|
||
char *timestamp;
|
||
int ret;
|
||
|
||
if (log_level > CAS_LOG_LEVEL)
|
||
return 0;
|
||
|
||
log = fopen(CAS_LOG_FILE, "a");
|
||
if (!log)
|
||
return FAILURE;
|
||
|
||
ret = lockf(fileno(log), F_LOCK, 0);
|
||
if (ret < 0)
|
||
goto out;
|
||
|
||
t = time(NULL);
|
||
tm = localtime(&t);
|
||
if (!tm) {
|
||
ret = FAILURE;
|
||
goto out;
|
||
}
|
||
|
||
timestamp = asctime(tm);
|
||
if (!timestamp) {
|
||
ret = FAILURE;
|
||
goto out;
|
||
}
|
||
|
||
timestamp[strnlen(timestamp, SIZE_MAX)-1] = 0;
|
||
|
||
fseek(log, 0, SEEK_END);
|
||
fprintf(log, "%s casadm: ", timestamp);
|
||
vfprintf(log, template, args);
|
||
fflush(log);
|
||
|
||
lockf(fileno(log), F_ULOCK, 0);
|
||
|
||
out:
|
||
fclose(log);
|
||
return ret;
|
||
}
|
||
|
||
__attribute__((format(printf, 2, 3)))
|
||
int caslog(int log_level, const char *template, ...)
|
||
{
|
||
va_list args;
|
||
va_start(args, template);
|
||
vcaslog(log_level, template, args);
|
||
va_end(args);
|
||
return 0;
|
||
}
|
||
|
||
__attribute__((format(printf, 2, 3)))
|
||
int std_printf(int log_level, const char *template, ...)
|
||
{
|
||
va_list args;
|
||
va_start(args, template);
|
||
if (LOG_WARNING >= log_level) {
|
||
va_list args_copy;
|
||
va_copy(args_copy, args);
|
||
vfprintf(stderr, template, args);
|
||
vcaslog(log_level, template, args_copy);
|
||
va_end(args_copy);
|
||
} else {
|
||
vfprintf(stdout, template, args);
|
||
}
|
||
va_end(args);
|
||
return 0;
|
||
}
|
||
|
||
cas_printf_t cas_printf = std_printf;
|
||
|
||
int validate_dev(const char *dev_path)
|
||
{
|
||
struct fstab *fstab_entry;
|
||
fstab_entry = getfsspec(dev_path);
|
||
if (fstab_entry != NULL) {
|
||
return FAILURE;
|
||
}
|
||
return SUCCESS;
|
||
}
|
||
|
||
int validate_path(const char *path, int exist)
|
||
{
|
||
if (NULL == path) {
|
||
return FAILURE;
|
||
}
|
||
|
||
if (0 == path[0]) {
|
||
cas_printf(LOG_ERR, "Empty path\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
if (strnlen(path, MAX_STR_LEN) >= MAX_STR_LEN) {
|
||
cas_printf(LOG_ERR, "File path too long\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
if (exist) {
|
||
struct stat _stat = { 0 };
|
||
int result = stat(path, &_stat);
|
||
if (result) {
|
||
cas_printf(LOG_ERR, "File does not exist\n");
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int __validate_str_num(const char *source_str, const char *msg,
|
||
long long int min, long long int max, bool validate_sbd)
|
||
{
|
||
uint64_t ret;
|
||
char *endptr = NULL;
|
||
|
||
errno = 0;
|
||
ret = strtoul(source_str, &endptr, 10);
|
||
if (endptr == source_str || (endptr && *endptr != '\0') ||
|
||
((ret == 0 || ret == ULONG_MAX) && errno)) {
|
||
cas_printf(LOG_ERR, "Invalid %s, must be a correct unsigned decimal integer.\n",
|
||
msg);
|
||
return FAILURE;
|
||
} else if (ret < min || ret > max) {
|
||
cas_printf(LOG_ERR, "Invalid %s, must be in the range %lld-%lld.\n",
|
||
msg, min, max);
|
||
return FAILURE;
|
||
} else if (validate_sbd && __builtin_popcount(ret) != 1) {
|
||
cas_printf(LOG_ERR, "Invalid %s, must be a power of 2.\n", msg);
|
||
return FAILURE;
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int validate_str_num(const char *source_str, const char *msg, long long int min, long long int max)
|
||
{
|
||
return __validate_str_num(source_str, msg, min, max, false);
|
||
}
|
||
|
||
int validate_str_num_sbd(const char *source_str, const char *msg, int min, int max)
|
||
{
|
||
return __validate_str_num(source_str, msg, min, max, true);
|
||
}
|
||
|
||
int validate_str_unum(const char *source_str, const char *msg, unsigned int min,
|
||
unsigned int max)
|
||
{
|
||
return __validate_str_num(source_str, msg, min, max, false);
|
||
}
|
||
|
||
struct name_to_val_mapping {
|
||
const char* short_name;
|
||
const char* long_name;
|
||
int value;
|
||
};
|
||
|
||
static struct name_to_val_mapping eviction_policy_names[] = {
|
||
{ .short_name = "lru", .value = ocf_eviction_lru },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping cache_mode_names[] = {
|
||
{ .short_name = "wt", .long_name = "Write-Through", .value = ocf_cache_mode_wt },
|
||
{ .short_name = "wb", .long_name = "Write-Back", .value = ocf_cache_mode_wb },
|
||
{ .short_name = "wa", .long_name = "Write-Around", .value = ocf_cache_mode_wa },
|
||
{ .short_name = "pt", .long_name = "Pass-Through", .value = ocf_cache_mode_pt },
|
||
#ifdef WI_AVAILABLE
|
||
{ .short_name = "wi", .long_name = "Write-Invalidate", .value = ocf_cache_mode_wi },
|
||
#endif
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping cleaning_policy_names[] = {
|
||
{ .short_name = "nop", .value = ocf_cleaning_nop },
|
||
{ .short_name = "alru", .value = ocf_cleaning_alru },
|
||
{ .short_name = "acp", .value = ocf_cleaning_acp },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping metadata_mode_names[] = {
|
||
{ .short_name = "normal", .value = CAS_METADATA_MODE_NORMAL },
|
||
{ .short_name = "atomic", .value = CAS_METADATA_MODE_ATOMIC },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping seq_cutoff_policy_names[] = {
|
||
{ .short_name = "always", .value = ocf_seq_cutoff_policy_always },
|
||
{ .short_name = "full", .value = ocf_seq_cutoff_policy_full },
|
||
{ .short_name = "never", .value = ocf_seq_cutoff_policy_never },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping stats_filters_names[] = {
|
||
{ .short_name = "conf", .value = STATS_FILTER_CONF },
|
||
{ .short_name = "usage", .value = STATS_FILTER_USAGE },
|
||
{ .short_name = "req", .value = STATS_FILTER_REQ },
|
||
{ .short_name = "blk", .value = STATS_FILTER_BLK },
|
||
{ .short_name = "err", .value = STATS_FILTER_ERR },
|
||
{ .short_name = "all", .value = STATS_FILTER_ALL },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping output_formats_names[] = {
|
||
{ .short_name = "table", .value = OUTPUT_FORMAT_TABLE },
|
||
{ .short_name = "csv", .value = OUTPUT_FORMAT_CSV },
|
||
{ NULL }
|
||
};
|
||
|
||
static struct name_to_val_mapping metadata_modes_names[] = {
|
||
{ .short_name = "normal", .value = METADATA_MODE_NORMAL },
|
||
{ .short_name = "atomic", .value = METADATA_MODE_ATOMIC },
|
||
{ NULL }
|
||
};
|
||
|
||
static int validate_str_val_mapping(const char* s,
|
||
const struct name_to_val_mapping* mappings,
|
||
int invalid_value)
|
||
{
|
||
int i;
|
||
|
||
if (strempty(s)) {
|
||
return invalid_value;
|
||
}
|
||
|
||
for (i = 0; NULL != mappings[i].short_name; ++i) {
|
||
if (0 == strncmp(mappings[i].short_name, s, MAX_STR_LEN)) {
|
||
return mappings[i].value;
|
||
}
|
||
}
|
||
|
||
return invalid_value;
|
||
}
|
||
|
||
static int validate_str_val_mapping_multi(const char* s,
|
||
const struct name_to_val_mapping* mappings,
|
||
int invalid_value)
|
||
{
|
||
const char* p;
|
||
char* token;
|
||
char* delim;
|
||
int value = 0;
|
||
int token_val;
|
||
|
||
if (strempty(s)) {
|
||
return invalid_value;
|
||
}
|
||
|
||
p = s;
|
||
while (p[0]) {
|
||
delim = strchr(p, ',');
|
||
if (delim == p) {
|
||
/* Empty tokens not allowed */
|
||
return invalid_value;
|
||
}
|
||
|
||
if (delim) {
|
||
token = strndup(p, delim - p);
|
||
/* Skip token and comma */
|
||
p = delim + 1;
|
||
if (!p[0]) {
|
||
/* Trailing comma not allowed */
|
||
free(token);
|
||
return invalid_value;
|
||
}
|
||
} else {
|
||
size_t len = strnlen(p, MAX_STR_LEN);
|
||
if (len >= MAX_STR_LEN) {
|
||
return invalid_value;
|
||
}
|
||
|
||
token = strdup(p);
|
||
p += len;
|
||
}
|
||
|
||
token_val = validate_str_val_mapping(token, mappings, invalid_value);
|
||
if (token_val == invalid_value) {
|
||
free(token);
|
||
return invalid_value;
|
||
}
|
||
|
||
value |= token_val;
|
||
free(token);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
static const char* val_to_long_name(int value, const struct name_to_val_mapping* mappings,
|
||
const char* other_name)
|
||
{
|
||
int i;
|
||
for (i = 0; NULL != mappings[i].long_name; ++i) {
|
||
if (mappings[i].value == value) {
|
||
return mappings[i].long_name;
|
||
}
|
||
}
|
||
return other_name;
|
||
}
|
||
|
||
static const char* val_to_short_name(int value, const struct name_to_val_mapping* mappings,
|
||
const char* other_name)
|
||
{
|
||
int i;
|
||
for (i = 0; NULL != mappings[i].short_name; ++i) {
|
||
if (mappings[i].value == value) {
|
||
return mappings[i].short_name;
|
||
}
|
||
}
|
||
return other_name;
|
||
}
|
||
|
||
/* Returns non-negative policy index or
|
||
* negative number in case of error.
|
||
*/
|
||
inline int validate_str_ev_policy(const char *s)
|
||
{
|
||
return validate_str_val_mapping(s, eviction_policy_names, -1);
|
||
}
|
||
|
||
inline const char *eviction_policy_to_name(uint8_t policy)
|
||
{
|
||
return val_to_short_name(policy, eviction_policy_names, "Unknown");
|
||
}
|
||
|
||
inline const char *cache_mode_to_name(uint8_t cache_mode)
|
||
{
|
||
return val_to_short_name(cache_mode, cache_mode_names, "Unknown");
|
||
}
|
||
|
||
static inline const char *cache_mode_to_name_long(uint8_t cache_mode)
|
||
{
|
||
return val_to_long_name(cache_mode, cache_mode_names, "??");
|
||
}
|
||
|
||
inline int validate_str_cache_mode(const char *s)
|
||
{
|
||
return validate_str_val_mapping(s, cache_mode_names, -1);
|
||
}
|
||
|
||
inline int validate_str_cln_policy(const char *s)
|
||
{
|
||
return validate_str_val_mapping(s, cleaning_policy_names, -1);
|
||
}
|
||
|
||
inline const char *cleaning_policy_to_name(uint8_t policy)
|
||
{
|
||
return val_to_short_name(policy, cleaning_policy_names, "Unknown");
|
||
}
|
||
|
||
const char *metadata_mode_to_name(uint8_t metadata_mode)
|
||
{
|
||
return val_to_short_name(metadata_mode, metadata_mode_names, "Invalid");
|
||
}
|
||
|
||
const char *seq_cutoff_policy_to_name(uint8_t seq_cutoff_policy)
|
||
{
|
||
return val_to_short_name(seq_cutoff_policy,
|
||
seq_cutoff_policy_names, "Invalid");
|
||
}
|
||
|
||
inline void metadata_memory_footprint(uint64_t size, float *footprint,
|
||
const char **units)
|
||
{
|
||
float factor = 1;
|
||
static const char *units_names[] = {"B", "KiB", "MiB", "GiB", "TiB"};
|
||
uint32_t i;
|
||
|
||
for (i = 0; i < sizeof(units_names) / sizeof(units_names[0]); i++) {
|
||
*footprint = ((float) (size)) / factor;
|
||
*units = units_names[i];
|
||
|
||
if (*footprint < 1024.0) {
|
||
break;
|
||
}
|
||
|
||
factor *= 1024;
|
||
}
|
||
}
|
||
|
||
/* Returns one of or combination of STATS_FILTER values
|
||
* or STATS_FILTER_INVALID in case of error.
|
||
*/
|
||
int validate_str_stats_filters(const char* s)
|
||
{
|
||
return validate_str_val_mapping_multi(s, stats_filters_names,
|
||
STATS_FILTER_INVALID);
|
||
}
|
||
|
||
/* Returns one of OUTPUT_FORMAT values
|
||
* or OUTPUT_FORMAT_INVALID in case of error.
|
||
*/
|
||
int validate_str_output_format(const char* s)
|
||
{
|
||
return validate_str_val_mapping(s, output_formats_names,
|
||
OUTPUT_FORMAT_INVALID);
|
||
}
|
||
|
||
/* Returns one of METADATA_MODE values
|
||
* or METADATA_MODE_INVALID in case of error.
|
||
*/
|
||
int validate_str_metadata_mode(const char* s)
|
||
{
|
||
return validate_str_val_mapping(s, metadata_modes_names,
|
||
METADATA_MODE_INVALID);
|
||
}
|
||
|
||
void print_err(int error_code)
|
||
{
|
||
const char *msg = cas_strerr(error_code);
|
||
|
||
if (msg)
|
||
cas_printf(LOG_ERR, "%s\n", msg);
|
||
}
|
||
|
||
const char *get_cache_state_name(int cache_state)
|
||
{
|
||
int i;
|
||
/* iterate over states in reverse order, so that combined states "running&stopping"
|
||
* would be described as "stopping" */
|
||
for (i = ocf_cache_state_max - 1; i >= 0; --i) {
|
||
if ((cache_state & (1 << i)) > 0) {
|
||
return cache_states_name[i];
|
||
}
|
||
}
|
||
return NOT_RUNNING_STATE;
|
||
}
|
||
|
||
const char *get_core_state_name(int core_state)
|
||
{
|
||
if (core_state < 0 || core_state >= ocf_core_state_max)
|
||
return "Invalid";
|
||
|
||
return core_states_name[core_state];
|
||
}
|
||
|
||
|
||
/* check if device is atomic and print information about potential slow start */
|
||
void print_slow_atomic_cache_start_info(const char *device_path)
|
||
{
|
||
struct kcas_cache_check_device cmd_info;
|
||
int ret = _check_cache_device(device_path, &cmd_info);
|
||
|
||
if (!ret && cmd_info.format_atomic) {
|
||
cas_printf(LOG_INFO,
|
||
"Starting new cache instance on a device with atomic metadata format may take \n"
|
||
"several minutes depending on device model and size.\n");
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief get special device file path (/dev/sdX) for disk.
|
||
*/
|
||
int get_dev_path(const char* disk, char* buf, size_t num)
|
||
{
|
||
char *path;
|
||
int err;
|
||
|
||
path = realpath(disk, NULL);
|
||
if (!path)
|
||
return FAILURE;
|
||
|
||
err = strncpy_s(buf, num, path, MAX_STR_LEN);
|
||
|
||
free(path);
|
||
return err;
|
||
}
|
||
|
||
int get_core_info(int fd, int cache_id, int core_id, struct kcas_core_info *info)
|
||
{
|
||
memset(info, 0, sizeof(*info));
|
||
info->cache_id = cache_id;
|
||
info->core_id = core_id;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_CORE_INFO, info) < 0) {
|
||
return FAILURE;
|
||
}
|
||
|
||
/* internally use device special file path to describe core */
|
||
if (get_dev_path(info->core_path_name,
|
||
info->core_path_name,
|
||
sizeof(info->core_path_name))) {
|
||
cas_printf(LOG_WARNING, "WARNING: Can not resolve path to core "
|
||
"%d from cache %d. By-id path will be shown for that core.\n",
|
||
core_id, cache_id);
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
static int get_core_device(int cache_id, int core_id, struct core_device *core)
|
||
{
|
||
int fd;
|
||
struct kcas_core_info cmd_info;
|
||
|
||
if (!core)
|
||
return FAILURE;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (get_core_info(fd, cache_id, core_id, &cmd_info)) {
|
||
cas_printf(LOG_ERR, "Error while retrieving stats\n");
|
||
print_err(cmd_info.ext_err_code);
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
close(fd);
|
||
|
||
core->id = core_id;
|
||
core->cache_id = cache_id;
|
||
strncpy_s(core->path, sizeof(core->path), cmd_info.core_path_name,
|
||
sizeof(cmd_info.core_path_name));
|
||
memcpy_s(&core->info, sizeof(core->info),
|
||
&cmd_info, sizeof(cmd_info));
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int get_cache_count(int fd)
|
||
{
|
||
struct kcas_cache_count cmd;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_GET_CACHE_COUNT, &cmd) < 0)
|
||
return 0;
|
||
|
||
return cmd.cache_count;
|
||
}
|
||
|
||
int *get_cache_ids(int *caches_count)
|
||
{
|
||
int i, fd, status;
|
||
struct kcas_cache_list cache_list;
|
||
int *cache_ids = NULL;
|
||
int count, chunk_size;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return NULL;
|
||
|
||
count = get_cache_count(fd);
|
||
|
||
if (count <= 0) {
|
||
goto error_out;
|
||
}
|
||
|
||
cache_ids = malloc(count * sizeof(*cache_ids));
|
||
if (cache_ids == NULL) {
|
||
goto error_out;
|
||
}
|
||
|
||
memset(&cache_list, 0, sizeof(cache_list));
|
||
|
||
*caches_count = 0;
|
||
|
||
chunk_size = CACHE_LIST_ID_LIMIT;
|
||
cache_list.id_position = 0;
|
||
cache_list.in_out_num = chunk_size;
|
||
do {
|
||
if ((status = ioctl(fd, KCAS_IOCTL_LIST_CACHE, &cache_list)) < 0) {
|
||
if (errno != EINVAL) {
|
||
cas_printf(LOG_ERR, "Error while retrieving cache properties %d %d\n",
|
||
errno, status);
|
||
free(cache_ids);
|
||
cache_ids = NULL;
|
||
*caches_count = 0;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* iterate through id table and get status */
|
||
for (i = 0; i < cache_list.in_out_num; i++) {
|
||
cache_ids[(*caches_count)] = cache_list.cache_id_tab[i];
|
||
(*caches_count)++;
|
||
if (*caches_count >= count) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
cache_list.id_position += chunk_size;
|
||
} while (cache_list.in_out_num >= chunk_size); /* repeat until there is no more devices on the list */
|
||
|
||
error_out:
|
||
close(fd);
|
||
return cache_ids;
|
||
}
|
||
|
||
/**
|
||
* @brief function returns pointer to cache device given cache_info structure.
|
||
*
|
||
* structure is mallocated, and therefore it is callers responsibility to free it.
|
||
*
|
||
* @return valid pointer to a structure or NULL if error happened
|
||
*/
|
||
struct cache_device *get_cache_device(const struct kcas_cache_info *info)
|
||
{
|
||
int core_id, cache_id, ret;
|
||
struct cache_device *cache;
|
||
struct core_device core;
|
||
cache_id = info->cache_id;
|
||
size_t cache_size;
|
||
|
||
cache_size = sizeof(*cache);
|
||
cache_size += info->info.core_count * sizeof(cache->cores[0]);
|
||
|
||
cache = (struct cache_device *) malloc(cache_size);
|
||
if (NULL == cache) {
|
||
return NULL;
|
||
}
|
||
|
||
cache->core_count = 0;
|
||
cache->expected_core_count = info->info.core_count;
|
||
cache->id = cache_id;
|
||
cache->state = info->info.state;
|
||
strncpy_s(cache->device, sizeof(cache->device), info->cache_path_name,
|
||
strnlen_s(info->cache_path_name, sizeof(info->cache_path_name)));
|
||
cache->mode = info->info.cache_mode;
|
||
cache->dirty = info->info.dirty;
|
||
cache->flushed = info->info.flushed;
|
||
cache->eviction_policy = info->info.eviction_policy;
|
||
cache->cleaning_policy = info->info.cleaning_policy;
|
||
cache->size = info->info.cache_line_size;
|
||
|
||
if ((info->info.state & (1 << ocf_cache_state_running)) == 0) {
|
||
return cache;
|
||
}
|
||
|
||
for (cache->core_count = 0; cache->core_count < info->info.core_count; ++cache->core_count) {
|
||
core_id = info->core_id[cache->core_count];
|
||
|
||
ret = get_core_device(cache_id, core_id, &core);
|
||
if (0 != ret) {
|
||
break;
|
||
} else {
|
||
memcpy_s(&cache->cores[cache->core_count],
|
||
sizeof(cache->cores[cache->core_count]),
|
||
&core, sizeof(core));
|
||
}
|
||
}
|
||
|
||
return cache;
|
||
}
|
||
|
||
/**
|
||
* @brief function returns pointer to cache device given cache id and fd of /dev/cas_ctrl
|
||
*
|
||
* structure is mallocated, and therefore it is callers responsibility to free it.
|
||
*
|
||
* @param fd valid file descriptor to /dev/cas_ctrl
|
||
* @param cache_id cache id (1...)
|
||
* @return valid pointer to a structure or NULL if error happened
|
||
*/
|
||
struct cache_device *get_cache_device_by_id_fd(int cache_id, int fd)
|
||
{
|
||
struct kcas_cache_info cmd_info;
|
||
|
||
memset(&cmd_info, 0, sizeof(cmd_info));
|
||
cmd_info.cache_id = cache_id;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_CACHE_INFO, &cmd_info) < 0) {
|
||
if (errno != EINVAL)
|
||
return NULL;
|
||
}
|
||
|
||
return get_cache_device(&cmd_info);
|
||
}
|
||
|
||
void free_cache_devices_list(struct cache_device **caches, int caches_count)
|
||
{
|
||
int i;
|
||
for (i = 0; i < caches_count; ++i) {
|
||
free(caches[i]);
|
||
caches[i] = NULL;
|
||
}
|
||
free(caches);
|
||
}
|
||
|
||
struct cache_device **get_cache_devices(int *caches_count)
|
||
{
|
||
int i, fd, status, chunk_size, count;
|
||
struct kcas_cache_list cache_list;
|
||
struct cache_device **caches = NULL;
|
||
struct cache_device *tmp_cache;
|
||
|
||
*caches_count = -1;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return NULL;
|
||
|
||
*caches_count = count = get_cache_count(fd);
|
||
if (count <= 0) {
|
||
goto error_out;
|
||
}
|
||
|
||
memset(&cache_list, 0, sizeof(cache_list));
|
||
caches = malloc(count * sizeof(*caches));
|
||
|
||
if (NULL == caches) {
|
||
*caches_count = -1;
|
||
goto error_out;
|
||
}
|
||
|
||
(*caches_count) = 0;
|
||
|
||
chunk_size = CACHE_LIST_ID_LIMIT;
|
||
|
||
cache_list.id_position = 0;
|
||
cache_list.in_out_num = chunk_size;
|
||
do {
|
||
if ((status = ioctl(fd, KCAS_IOCTL_LIST_CACHE, &cache_list)) < 0) {
|
||
if (errno != EINVAL) {
|
||
cas_printf(LOG_ERR, "Error while retrieving cache properties %d %d\n",
|
||
errno, status);
|
||
free_cache_devices_list(caches, *caches_count);
|
||
*caches_count = -1;
|
||
caches = NULL;
|
||
goto error_out;
|
||
}
|
||
}
|
||
|
||
/* iterate through id table and get status */
|
||
for (i = 0; i < cache_list.in_out_num; i++) {
|
||
if ((tmp_cache = get_cache_device_by_id_fd(cache_list.cache_id_tab[i], fd)) == NULL) {
|
||
cas_printf(LOG_ERR, "Failed to retrieve cache information!\n");
|
||
continue;
|
||
}
|
||
caches[(*caches_count)] = tmp_cache;
|
||
(*caches_count)++;
|
||
if (*caches_count >= count) {
|
||
break;
|
||
}
|
||
}
|
||
cache_list.id_position += chunk_size;
|
||
} while (cache_list.in_out_num >= chunk_size); /* repeat until there is no more devices on the list */
|
||
|
||
error_out:
|
||
close(fd);
|
||
return caches;
|
||
}
|
||
|
||
int caches_compare(const void *a, const void *b)
|
||
{
|
||
int a_id = (*(struct cache_device**)a)->id;
|
||
int b_id = (*(struct cache_device**)b)->id;
|
||
return a_id - b_id;
|
||
}
|
||
|
||
int check_cache_already_added(const char *cache_device) {
|
||
struct cache_device **caches, *curr_cache;
|
||
int caches_count, i;
|
||
|
||
caches = get_cache_devices(&caches_count);
|
||
|
||
if (NULL == caches) {
|
||
return SUCCESS;
|
||
}
|
||
|
||
for (i = 0; i < caches_count; ++i) {
|
||
curr_cache = caches[i];
|
||
if (0 == strncmp(cache_device, curr_cache->device, MAX_STR_LEN)) {
|
||
free_cache_devices_list(caches, caches_count);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
free_cache_devices_list(caches, caches_count);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
static void check_cache_scheduler(const char *cache_device, const char *elv_name)
|
||
{
|
||
if (strnlen_s(elv_name, MAX_ELEVATOR_NAME) == 3 &&
|
||
!strncmp(elv_name, "cfq", 3)) {
|
||
cas_printf(LOG_INFO,
|
||
"I/O scheduler for cache device %s is %s. This could cause performance drop.\n"
|
||
"Consider switching I/O scheduler to deadline or noop.\n",
|
||
cache_device, elv_name);
|
||
}
|
||
}
|
||
|
||
int start_cache(ocf_cache_id_t cache_id, unsigned int cache_init,
|
||
const char *cache_device, ocf_cache_mode_t cache_mode,
|
||
ocf_eviction_t eviction_policy_type,
|
||
ocf_cache_line_size_t line_size, int force)
|
||
{
|
||
int fd = 0;
|
||
struct kcas_start_cache cmd;
|
||
struct cache_device **caches;
|
||
struct cache_device *cache;
|
||
int i, status, caches_count;
|
||
double min_free_ram_gb;
|
||
|
||
/* check if cache device given exists */
|
||
fd = open(cache_device, 0);
|
||
if (fd < 0) {
|
||
cas_printf(LOG_ERR, "Device %s not found.\n", cache_device);
|
||
return FAILURE;
|
||
}
|
||
close(fd);
|
||
|
||
if (cache_init == CACHE_INIT_NEW)
|
||
print_slow_atomic_cache_start_info(cache_device);
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (cache_id == 0) {
|
||
cache_id = 1;
|
||
caches = get_cache_devices(&caches_count);
|
||
if (caches != NULL) {
|
||
psort(caches, caches_count, sizeof(struct cache_device*), caches_compare);
|
||
for (i = 0; i < caches_count; ++i) {
|
||
if (caches[i]->id == cache_id) {
|
||
cache_id += 1;
|
||
}
|
||
}
|
||
|
||
free_cache_devices_list(caches, caches_count);
|
||
}
|
||
}
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.init_cache = cache_init;
|
||
strncpy_s(cmd.cache_path_name,
|
||
sizeof(cmd.cache_path_name),
|
||
cache_device,
|
||
strnlen_s(cache_device,
|
||
sizeof(cmd.cache_path_name)));
|
||
cmd.caching_mode = cache_mode;
|
||
cmd.eviction_policy = eviction_policy_type;
|
||
cmd.line_size = line_size;
|
||
cmd.force = (uint8_t)force;
|
||
|
||
if (run_ioctl_interruptible(fd, KCAS_IOCTL_START_CACHE, &cmd,
|
||
"Starting cache", cache_id, OCF_CORE_ID_INVALID) < 0) {
|
||
close(fd);
|
||
if (cmd.ext_err_code == OCF_ERR_NO_FREE_RAM) {
|
||
min_free_ram_gb = cmd.min_free_ram;
|
||
min_free_ram_gb /= GiB;
|
||
|
||
cas_printf(LOG_ERR, "Not enough free RAM.\n"
|
||
"You need at least %0.2gGB to start cache"
|
||
" with cache line size equal %llukB.\n",
|
||
min_free_ram_gb, line_size / KiB);
|
||
|
||
if (64 * KiB > line_size)
|
||
cas_printf(LOG_ERR, "Try with greater cache line size.\n");
|
||
|
||
return FAILURE;
|
||
} else {
|
||
cas_printf(LOG_ERR, "Error inserting cache %d\n", cache_id);
|
||
if (OCF_ERR_NOT_OPEN_EXC == cmd.ext_err_code &&
|
||
FAILURE == check_cache_already_added(cache_device)) {
|
||
cas_printf(LOG_ERR, "Cache device '%s' is already used as cache.\n",
|
||
cache_device);
|
||
} else {
|
||
print_err(cmd.ext_err_code);
|
||
}
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
if (!cmd.metadata_mode_optimal)
|
||
cas_printf(LOG_NOTICE, "Selected metadata mode is not optimal for device %s.\n"
|
||
"You can improve cache performance by formating your device\n"
|
||
"to use optimal metadata mode with following command:\n"
|
||
"casadm --nvme --format atomic --device %s\n",
|
||
cache_device, cache_device);
|
||
|
||
check_cache_scheduler(cache_device,
|
||
cmd.cache_elevator);
|
||
|
||
status = SUCCESS;
|
||
|
||
for (i = 0; i < CORE_ADD_MAX_TIMEOUT; ++i) {
|
||
cache = get_cache_device_by_id_fd(cache_id, fd);
|
||
status = FAILURE;
|
||
|
||
if (cache == NULL) {
|
||
break;
|
||
}
|
||
|
||
if (cache->core_count == cache->expected_core_count) {
|
||
if (cache->state & (1 << ocf_cache_state_incomplete)) {
|
||
cas_printf(LOG_WARNING, "WARNING: Cache is in incomplete state - at least one core is inactive\n");
|
||
}
|
||
status = SUCCESS;
|
||
free(cache);
|
||
cache = NULL;
|
||
break;
|
||
}
|
||
|
||
free(cache);
|
||
cache = NULL;
|
||
|
||
sleep(1);
|
||
}
|
||
|
||
close(fd);
|
||
|
||
if (status == SUCCESS) {
|
||
cas_printf(LOG_INFO, "Successfully added cache instance %u\n", cache_id);
|
||
} else {
|
||
cas_printf(LOG_ERR, "Failed to start cache\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int stop_cache(ocf_cache_id_t cache_id, int flush)
|
||
{
|
||
int fd = 0;
|
||
struct kcas_stop_cache cmd;
|
||
|
||
/* don't even attempt ioctl if filesystem is mounted */
|
||
if (check_if_mounted(cache_id, CHECK_IF_CACHE_IS_MOUNTED) == FAILURE) {
|
||
return FAILURE;
|
||
}
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
cmd.flush_data = flush;
|
||
|
||
if(run_ioctl_interruptible(fd, KCAS_IOCTL_STOP_CACHE, &cmd, "Stopping cache",
|
||
cache_id, OCF_CORE_ID_INVALID) < 0) {
|
||
close(fd);
|
||
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
|
||
cas_printf(LOG_ERR, "You have interrupted stopping of cache. CAS continues\n"
|
||
"to operate normally. If you want to stop cache without fully\n"
|
||
"flushing dirty data, use '-n' option.\n");
|
||
return INTERRUPTED;
|
||
} else {
|
||
cas_printf(LOG_ERR, "Error while removing cache %d\n", cache_id);
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
/*
|
||
* @brief check caching mode
|
||
* @param[in] ctrl_fd file descriptor of opened control utility
|
||
* @param[in] cache_id id of cache device
|
||
* @param[out] mode mode identifier as integer
|
||
* @return exit code of successful completion is 0; nonzero exit code means failure
|
||
*/
|
||
int get_cache_mode(int ctrl_fd, unsigned int cache_id, int *mode)
|
||
{
|
||
struct kcas_cache_info cmd_info;
|
||
|
||
memset(&cmd_info, 0, sizeof(cmd_info));
|
||
cmd_info.cache_id = cache_id;
|
||
|
||
if (ioctl(ctrl_fd, KCAS_IOCTL_CACHE_INFO, &cmd_info) < 0)
|
||
return FAILURE;
|
||
|
||
*mode = cmd_info.info.cache_mode;
|
||
return SUCCESS;
|
||
}
|
||
|
||
int set_cache_mode(unsigned int cache_mode, unsigned int cache_id, int flush)
|
||
{
|
||
int fd = 0;
|
||
int orig_mode;
|
||
struct kcas_set_cache_state cmd;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (get_cache_mode(fd, cache_id, &orig_mode)) {
|
||
cas_printf(LOG_ERR, "Error while retrieving cache properties.\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
/* if flushing mode is undefined, set it to default (but only if original mode is write back mode) */
|
||
if (-1 == flush) {
|
||
if (ocf_cache_mode_wb == orig_mode) {
|
||
cas_printf(LOG_ERR, "Error: Required parameter (‘--flush-cache’) was not specified.\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
} else {
|
||
flush=NO;
|
||
}
|
||
}
|
||
|
||
if (ocf_cache_mode_wb == orig_mode) {
|
||
if (1 == flush) {
|
||
cas_printf(LOG_INFO, "CAS is currently flushing dirty data to primary storage devices.\n");
|
||
} else {
|
||
cas_printf(LOG_INFO, "CAS is currently migrating from Write-Back to %s mode.\n"
|
||
"Dirty data are being flushed to primary storage device in background.\n"
|
||
"Please find flushing progress via list caches command (‘casadm -L’) or\n"
|
||
"via statistics command (‘casadm -P’).\n",
|
||
cache_mode_to_name_long(cache_mode));
|
||
}
|
||
}
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
cmd.caching_mode = cache_mode;
|
||
cmd.flush_data = flush;
|
||
|
||
if (run_ioctl_interruptible(fd, KCAS_IOCTL_SET_CACHE_STATE, &cmd, "Setting mode",
|
||
cache_id, OCF_CORE_ID_INVALID) < 0) {
|
||
close(fd);
|
||
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
|
||
assert(flush);
|
||
cas_printf(LOG_ERR,
|
||
"Interrupted flushing of dirty data. Software prevented switching\n"
|
||
"of cache mode. If you want to switch cache mode immediately, use\n"
|
||
"'--flush-cache no' parameter.\n");
|
||
return INTERRUPTED;
|
||
} else {
|
||
cas_printf(LOG_ERR, "Error while setting cache state for cache %d\n",
|
||
cache_id);
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
close(fd);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
static void print_param(FILE *intermediate_file, struct cas_param *param)
|
||
{
|
||
if (param->value_names) {
|
||
fprintf(intermediate_file, "%s%s,%s\n", TAG(TABLE_ROW),
|
||
param->name, param->value_names[param->value]);
|
||
} else {
|
||
char *unit = param->unit ?: "";
|
||
fprintf(intermediate_file, "%s%s,%u %s\n", TAG(TABLE_ROW),
|
||
param->name, param->value, unit);
|
||
}
|
||
fflush(intermediate_file);
|
||
}
|
||
|
||
int core_params_set(unsigned int cache_id, unsigned int core_id,
|
||
struct cas_param *params)
|
||
{
|
||
int cache_mode = ocf_cache_mode_none;
|
||
struct kcas_set_core_param cmd = {0};
|
||
int fd = 0;
|
||
int i;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (get_cache_mode(fd, cache_id, &cache_mode)) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (ocf_cache_mode_pt == cache_mode) {
|
||
cas_printf(LOG_WARNING, "Changing parameters for core in Pass-Through mode."
|
||
"New values will be saved but will not be effective"
|
||
"until switching to another cache mode.\n");
|
||
}
|
||
|
||
for (i = 0; params[i].name; ++i) {
|
||
if (!params[i].select)
|
||
continue;
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
cmd.param_id = i;
|
||
cmd.param_value = params[i].value;
|
||
|
||
if (run_ioctl(fd, KCAS_IOCTL_SET_CORE_PARAM, &cmd) < 0) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
int core_params_get(unsigned int cache_id, unsigned int core_id,
|
||
struct cas_param *params, unsigned int output_format)
|
||
{
|
||
struct kcas_get_core_param cmd = {0};
|
||
FILE *intermediate_file[2];
|
||
int fd = 0;
|
||
int i;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (create_pipe_pair(intermediate_file)) {
|
||
cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
fprintf(intermediate_file[1], TAG(TABLE_HEADER) "Parameter name,Value\n");
|
||
fflush(intermediate_file[1]);
|
||
|
||
for (i = 0; params[i].name; ++i) {
|
||
if (!params[i].select)
|
||
continue;
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
cmd.param_id = i;
|
||
|
||
if (run_ioctl(fd, KCAS_IOCTL_GET_CORE_PARAM, &cmd) < 0) {
|
||
if (cmd.ext_err_code == OCF_ERR_CACHE_NOT_EXIST)
|
||
cas_printf(LOG_ERR, "Cache id %d not running\n", cache_id);
|
||
else if (cmd.ext_err_code == OCF_ERR_CORE_NOT_AVAIL)
|
||
cas_printf(LOG_ERR, "Core id %d not available\n", core_id);
|
||
else
|
||
cas_printf(LOG_ERR, "Can't get parameters\n");
|
||
fclose(intermediate_file[0]);
|
||
fclose(intermediate_file[1]);
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (params[i].transform_value)
|
||
params[i].value = params[i].transform_value(cmd.param_value);
|
||
else
|
||
params[i].value = cmd.param_value;
|
||
|
||
print_param(intermediate_file[1], ¶ms[i]);
|
||
}
|
||
|
||
close(fd);
|
||
|
||
fclose(intermediate_file[1]);
|
||
stat_format_output(intermediate_file[0], stdout, output_format);
|
||
fclose(intermediate_file[0]);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int cache_params_set(unsigned int cache_id, struct cas_param *params)
|
||
{
|
||
int cache_mode = ocf_cache_mode_none;
|
||
struct kcas_set_cache_param cmd = {0};
|
||
int fd = 0;
|
||
int i;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (get_cache_mode(fd, cache_id, &cache_mode)) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (ocf_cache_mode_pt == cache_mode) {
|
||
cas_printf(LOG_WARNING, "Changing parameters for core in Pass-Through mode."
|
||
" New values will be saved but will not be effective"
|
||
" until switching to another cache mode.\n");
|
||
}
|
||
|
||
for (i = 0; params[i].name; ++i) {
|
||
if (!params[i].select)
|
||
continue;
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.param_id = i;
|
||
cmd.param_value = params[i].value;
|
||
|
||
if (run_ioctl(fd, KCAS_IOCTL_SET_CACHE_PARAM, &cmd) < 0) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
int cache_get_param(unsigned int cache_id, unsigned int param_id,
|
||
struct cas_param *param)
|
||
{
|
||
struct kcas_get_cache_param cmd = { 0 };
|
||
int fd = 0;
|
||
|
||
if (param_id >= cache_param_id_max)
|
||
return FAILURE;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
cmd.param_id = param_id;
|
||
cmd.cache_id = cache_id;
|
||
|
||
if (run_ioctl(fd, KCAS_IOCTL_GET_CACHE_PARAM, &cmd) < 0) {
|
||
if (cmd.ext_err_code == OCF_ERR_CACHE_NOT_EXIST)
|
||
cas_printf(LOG_ERR, "Cache id %d not running\n", cache_id);
|
||
else
|
||
cas_printf(LOG_ERR, "Can't get parameters\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (param->transform_value)
|
||
param->value = param->transform_value(cmd.param_value);
|
||
else
|
||
param->value = cmd.param_value;
|
||
|
||
close(fd);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int cache_params_get(unsigned int cache_id, struct cas_param *params,
|
||
unsigned int output_format)
|
||
{
|
||
struct kcas_get_cache_param cmd = {0};
|
||
FILE *intermediate_file[2];
|
||
int fd = 0;
|
||
int i;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (create_pipe_pair(intermediate_file)) {
|
||
cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
fprintf(intermediate_file[1], TAG(TABLE_HEADER) "Parameter name,Value\n");
|
||
fflush(intermediate_file[1]);
|
||
|
||
for (i = 0; params[i].name; ++i) {
|
||
if (!params[i].select)
|
||
continue;
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.param_id = i;
|
||
|
||
if (run_ioctl(fd, KCAS_IOCTL_GET_CACHE_PARAM, &cmd) < 0) {
|
||
if (cmd.ext_err_code == OCF_ERR_CACHE_NOT_EXIST)
|
||
cas_printf(LOG_ERR, "Cache id %d not running\n", cache_id);
|
||
else
|
||
cas_printf(LOG_ERR, "Can't get parameters\n");
|
||
fclose(intermediate_file[0]);
|
||
fclose(intermediate_file[1]);
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (params[i].transform_value)
|
||
params[i].value = params[i].transform_value(cmd.param_value);
|
||
else
|
||
params[i].value = cmd.param_value;
|
||
|
||
print_param(intermediate_file[1], ¶ms[i]);
|
||
}
|
||
|
||
close(fd);
|
||
|
||
fclose(intermediate_file[1]);
|
||
stat_format_output(intermediate_file[0], stdout, output_format);
|
||
fclose(intermediate_file[0]);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int check_core_already_cached(const char *core_device) {
|
||
struct cache_device **caches, *curr_cache;
|
||
struct core_device *curr_core;
|
||
int caches_count, i, j;
|
||
char core_device_path[MAX_STR_LEN];
|
||
|
||
if (get_dev_path(core_device, core_device_path, sizeof(core_device_path)))
|
||
return SUCCESS;
|
||
|
||
caches = get_cache_devices(&caches_count);
|
||
|
||
if (NULL == caches) {
|
||
return SUCCESS;
|
||
}
|
||
|
||
for (i = 0; i < caches_count; ++i) {
|
||
curr_cache = caches[i];
|
||
for (j = 0; j < curr_cache->core_count; ++j) {
|
||
curr_core = &curr_cache->cores[j];
|
||
if (0 ==
|
||
strncmp(core_device_path, curr_core->path, MAX_STR_LEN)) {
|
||
free_cache_devices_list(caches, caches_count);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
}
|
||
|
||
free_cache_devices_list(caches, caches_count);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
/**
|
||
* @brief convert string to int
|
||
*
|
||
* @param[in] start string beginning
|
||
* @param[out] end optional pointer to character past the end of integer
|
||
* @param[out] val integer value
|
||
* @return true in case of success, false in case of failure
|
||
*/
|
||
bool str_to_int(const char* start, char** end, int *val)
|
||
{
|
||
long int _val;
|
||
char *_end = (char *)start;
|
||
|
||
_val = strtol(start, &_end, 10);
|
||
|
||
if (_end == start) {
|
||
/* no integer found */
|
||
return false;
|
||
}
|
||
|
||
if (_val < INT_MIN || _val > INT_MAX) {
|
||
/* value out of int range */
|
||
return false;
|
||
}
|
||
|
||
/* 0 might indicate strtol error, so try to check if the
|
||
input is really 0. This might not be bullet-proof, but enough
|
||
for us. */
|
||
if (_val == 0 && *(_end - 1) != '0') {
|
||
/* doesn't look like 0, more likely a parsing error */
|
||
return false;
|
||
}
|
||
|
||
*val = (int)_val;
|
||
if (end)
|
||
*end = _end;
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
static bool get_core_cache_id_from_string(char *str,
|
||
int *cache_id, int *core_id)
|
||
{
|
||
char *end;
|
||
|
||
if (!str_to_int(str, &end, cache_id))
|
||
return false;
|
||
|
||
if (*end != '-') {
|
||
/* invalid separator */
|
||
return false;
|
||
}
|
||
|
||
if (!str_to_int(end + 1, NULL, core_id))
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
|
||
int get_inactive_core_count(const struct kcas_cache_info *cache_info)
|
||
{
|
||
struct cache_device *cache;
|
||
int inactive_cores = 0;
|
||
int i;
|
||
|
||
cache = get_cache_device(cache_info);
|
||
if (!cache)
|
||
return -1;
|
||
|
||
for (i = 0; i < cache->core_count; i++) {
|
||
if (cache->cores[i].info.state == ocf_core_state_inactive)
|
||
inactive_cores++;
|
||
}
|
||
|
||
free(cache);
|
||
|
||
return inactive_cores;
|
||
}
|
||
|
||
|
||
/**
|
||
* @brief check for illegal recursive core configuration
|
||
*
|
||
* Function returns 1 (FAILURE/true) if it detects that adding core_device to
|
||
* cache_id will result in illegal multilevel configuration.
|
||
* Function returns 0 (SUCCESS/false) if it detects that it is fine to add
|
||
* core_device to cache_id and it will NOT result in illegal multilevel
|
||
* configuration.
|
||
*
|
||
* Here is example of such illegal configuration:
|
||
*
|
||
* type id disk device
|
||
* cache 1 /dev/sdc1 -
|
||
* +core 1 /dev/sdd1 /dev/cas1-1
|
||
* +core 2 /dev/cas1-1 /dev/cas1-2
|
||
*
|
||
* Here is another example of illegal configuration (notice that it is indirect, and hence
|
||
* whole multilevel caching hierarchy has to be parsed)
|
||
*
|
||
* type id disk device
|
||
* cache 1 /dev/sdc1 -
|
||
* +core 1 /dev/sdd1 /dev/cas1-1
|
||
* +core 2 /dev/cas2-1 /dev/cas1-2
|
||
* cache 2 /dev/sdc2 -
|
||
* +core 1 /dev/cas1-1 /dev/cas2-1
|
||
*
|
||
* (in above example adding core 2 to cache shouldn't be allowed as this is effectively adding same
|
||
* disk device (/dev/sdd1) to the same cache (/dev/sdc1) twice).
|
||
*
|
||
* @param cache_id cache to which new core is being added
|
||
* @param core_device path to a core device that is being added
|
||
* @param fd valid file descriptor for /dev/cas_ctrl device
|
||
* @return 0 if check is successful and on illegal recursion is detected.
|
||
* 1 if illegal config detected.
|
||
*/
|
||
int illegal_recursive_core(unsigned int cache_id, const char *core_device, int core_path_size, int fd)
|
||
{
|
||
char tmp_path[MAX_STR_LEN];
|
||
char core_path[MAX_STR_LEN]; /* extracted actual path */
|
||
int dev_core_id, dev_cache_id; /* cache_id and core_id for currently
|
||
* analyzed device */
|
||
struct stat st_buf;
|
||
int i;
|
||
static const char cas_pattern[] = "/dev/cas";
|
||
struct cache_device *cache; /*structure containing data on cache device*/
|
||
|
||
while (true) {
|
||
/*
|
||
* if core_device is an cas device (or a symlink to
|
||
* cas device) check if its cache device is cache id. if
|
||
* it is, return an error, as this will lead to illegal
|
||
* multilevel configuration.
|
||
*/
|
||
if (lstat(core_device, &st_buf)) {
|
||
cas_printf(LOG_ERR, "ERROR: lstat failed for %s.\n",
|
||
core_device);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (get_dev_path(core_device, core_path, sizeof(core_path)))
|
||
return FAILURE;
|
||
|
||
/* if core_path does NOT begin with /dev/cas, report success
|
||
* as it certainly is not case of */
|
||
if (strncmp(cas_pattern, core_path, sizeof(cas_pattern) - 1)) {
|
||
return SUCCESS;
|
||
}
|
||
|
||
if (!get_core_cache_id_from_string(
|
||
core_path + sizeof(cas_pattern) - 1,
|
||
&dev_cache_id,
|
||
&dev_core_id)) {
|
||
cas_printf(LOG_ERR, "Failed to extract core/cache "
|
||
"id from %s path\n", core_path);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (dev_cache_id == cache_id) {
|
||
cas_printf(LOG_ERR, "Core device '%s' is already cached"
|
||
" on cache device %d. - "
|
||
"illegal multilevel caching configuration.\n",
|
||
core_device, cache_id);
|
||
return FAILURE;
|
||
}
|
||
/* possibly legal multilevel caching configuration - do one more
|
||
* iteration of this loop*/
|
||
|
||
/* get underlying core device of dev_cache_id-dev_core_id */
|
||
cache = get_cache_device_by_id_fd(dev_cache_id, fd);
|
||
|
||
if (!cache) {
|
||
cas_printf(LOG_ERR, "Failed to extract statistics for "
|
||
"cache device %d\n", dev_cache_id);
|
||
return FAILURE;
|
||
}
|
||
|
||
/* lookup for record for appropriate core */
|
||
for (i = 0; i != cache->core_count ; ++i) {
|
||
if (cache->cores[i].id == dev_core_id) {
|
||
strncpy_s(tmp_path, sizeof(tmp_path),
|
||
cache->cores[i].path,
|
||
strnlen_s(cache->cores[i].path,
|
||
sizeof(cache->cores[i].path)));
|
||
core_device = tmp_path;
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* make sure that loop above resulted in correct assignment */
|
||
if (i == cache->core_count) {
|
||
cas_printf(LOG_ERR, "Failed to extract statistics for "
|
||
"core device %d-%d. Does it exist?\n",
|
||
dev_cache_id, dev_core_id);
|
||
free(cache);
|
||
return FAILURE;
|
||
}
|
||
|
||
free(cache);
|
||
}
|
||
}
|
||
|
||
/* Indicate whether given entry in /dev/disk/by-id should be ignored -
|
||
we ignore software created links like 'lvm-' since these can point to
|
||
both CAS exported object and core device depending on initialization order.
|
||
*/
|
||
static bool dev_link_blacklisted(const char* entry)
|
||
{
|
||
static const char* const prefix_blacklist[] = {"lvm"};
|
||
static const unsigned count = ARRAY_SIZE(prefix_blacklist);
|
||
const char* curr;
|
||
unsigned i;
|
||
|
||
for (i = 0; i < count; i++) {
|
||
curr = prefix_blacklist[i];
|
||
if (!strncmp(entry, curr, strnlen_s(curr, MAX_STR_LEN)))
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/* get device link starting with /dev/disk/by-id */
|
||
static int get_dev_link(const char* disk, char* buf, size_t num)
|
||
{
|
||
static const char dev_by_id_dir[] = "/dev/disk/by-id";
|
||
int err;
|
||
struct dirent *entry;
|
||
DIR* dir;
|
||
char disk_dev[MAX_STR_LEN]; /* input disk device file */
|
||
char dev_by_id[MAX_STR_LEN]; /* current device path by id */
|
||
char curr_dev[MAX_STR_LEN]; /* current device file - compared against disk_dev[] */
|
||
int n;
|
||
|
||
dir = opendir(dev_by_id_dir);
|
||
if (!dir) {
|
||
/* no disk available by id? */
|
||
cas_printf(LOG_WARNING, "Unable to open disk alias directory.\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
if (get_dev_path(disk, disk_dev, sizeof(disk_dev))) {
|
||
err = FAILURE;
|
||
goto close_dir;
|
||
}
|
||
|
||
err = FAILURE;
|
||
while (err != SUCCESS && (entry = readdir(dir))) {
|
||
/* check if link is blacklisted */
|
||
if (dev_link_blacklisted(entry->d_name))
|
||
continue;
|
||
|
||
/* construct device-by-id path for current device */
|
||
n = snprintf(dev_by_id, sizeof(dev_by_id), "%s/%s",
|
||
dev_by_id_dir, entry->d_name);
|
||
if (n < 0 || n >= sizeof(dev_by_id)) {
|
||
cas_printf(LOG_WARNING,
|
||
"Error constructing disk device by-link path.\n");
|
||
continue;
|
||
}
|
||
/* get device path for current device */
|
||
if (get_dev_path(dev_by_id, curr_dev, sizeof(curr_dev))) {
|
||
/* it's normal to have stale links in /dev/ - no log */
|
||
continue;
|
||
}
|
||
/* compare current device path against disk device path */
|
||
if (!strncmp(disk_dev, curr_dev, sizeof(curr_dev))) {
|
||
if (n >= num) {
|
||
cas_printf(LOG_WARNING, "Buffer to short to store device link.\n");
|
||
} else {
|
||
strncpy_s(buf, num, dev_by_id, sizeof(dev_by_id));
|
||
err = SUCCESS;
|
||
}
|
||
}
|
||
}
|
||
|
||
close_dir:
|
||
closedir(dir);
|
||
|
||
return err;
|
||
}
|
||
|
||
static int set_core_path(char *path, const char *core_device, size_t len)
|
||
{
|
||
/* attempt to get disk device path by id */
|
||
if (get_dev_link(core_device, path, len) == SUCCESS)
|
||
return SUCCESS;
|
||
|
||
/* .. if this failed, try to get standard /dev/sd* path */
|
||
if (get_dev_path(core_device, path, len) == SUCCESS)
|
||
return SUCCESS;
|
||
|
||
/* if everything else failed - fall back to user-provided path */
|
||
if (!strncpy_s(path, len, core_device, strnlen_s(core_device, MAX_STR_LEN)))
|
||
return SUCCESS;
|
||
|
||
return FAILURE;
|
||
}
|
||
|
||
int add_core(unsigned int cache_id, unsigned int core_id, const char *core_device,
|
||
int try_add, int update_path)
|
||
{
|
||
int fd = 0, user_core_path_size;
|
||
struct kcas_insert_core cmd;
|
||
struct stat query_core;
|
||
const char *core_path; /* core path sent down to kernel */
|
||
const char *user_core_path; /* core path provided by user */
|
||
|
||
/* Check if core device provided is valid */
|
||
fd = open(core_device, 0);
|
||
if (fd < 0) {
|
||
cas_printf(LOG_ERR, "Device %s not found.\n", core_device);
|
||
return FAILURE;
|
||
}
|
||
close(fd);
|
||
|
||
/* Check if the core device is a block device or a file */
|
||
if (stat(core_device, &query_core)) {
|
||
cas_printf(LOG_ERR, "Could not stat target core device %s!\n", core_device);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (!S_ISBLK(query_core.st_mode)) {
|
||
cas_printf(LOG_ERR, "Core object %s is not supported!\n", core_device);
|
||
return FAILURE;
|
||
}
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
if (set_core_path(cmd.core_path_name, core_device, MAX_STR_LEN) != SUCCESS) {
|
||
cas_printf(LOG_ERR, "Failed to copy core path\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
user_core_path = core_device;
|
||
user_core_path_size = strnlen_s(core_device, MAX_STR_LEN);
|
||
core_path = cmd.core_path_name;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
/* check for illegal rec ursive caching config. */
|
||
if (illegal_recursive_core(cache_id, user_core_path,
|
||
user_core_path_size, fd)) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
cmd.try_add = try_add;
|
||
cmd.update_path = update_path;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_INSERT_CORE, &cmd) < 0) {
|
||
close(fd);
|
||
cas_printf(LOG_ERR, "Error while adding core device to cache instance %d\n",
|
||
cache_id);
|
||
if (OCF_ERR_NOT_OPEN_EXC == cmd.ext_err_code) {
|
||
if (FAILURE == check_core_already_cached(core_path)) {
|
||
cas_printf(LOG_ERR, "Core device '%s' is already cached.\n",
|
||
user_core_path);
|
||
} else {
|
||
cas_printf(LOG_ERR, "Failed to open '%s' device"
|
||
" exclusively. Please close all applications "
|
||
"accessing it or unmount the device.\n",
|
||
user_core_path);
|
||
}
|
||
} else {
|
||
print_err(cmd.ext_err_code);
|
||
}
|
||
return FAILURE;
|
||
}
|
||
close(fd);
|
||
|
||
if (try_add) {
|
||
cas_printf(LOG_INFO, "Successfully added device in try add mode %s\n", user_core_path);
|
||
} else {
|
||
core_id = cmd.core_id;
|
||
|
||
cas_printf(LOG_INFO, "Successfully added core %u to cache instance %u\n", core_id, cache_id);
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int check_if_mounted(int cache_id, int core_id)
|
||
{
|
||
FILE *mtab;
|
||
struct mntent *mstruct;
|
||
char dev_buf[80];
|
||
int dev_buf_len;
|
||
if (0 <= core_id) {
|
||
/* verify if specific core is mounted */
|
||
snprintf(dev_buf, sizeof(dev_buf), "/dev/cas%d-%d", cache_id, core_id);
|
||
} else {
|
||
/* verify if any core from given cache is mounted */
|
||
snprintf(dev_buf, sizeof(dev_buf), "/dev/cas%d-", cache_id);
|
||
}
|
||
dev_buf_len = strnlen(dev_buf, sizeof(dev_buf));
|
||
|
||
mtab = setmntent("/etc/mtab", "r");
|
||
if (!mtab)
|
||
{
|
||
cas_printf(LOG_ERR, "Error while accessing /etc/mtab\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
while ((mstruct = getmntent(mtab)) != NULL) {
|
||
/* mstruct->mnt_fsname is /dev/... block device path, not a mountpoint */
|
||
if ((NULL != mstruct->mnt_fsname)
|
||
&& (strncmp(mstruct->mnt_fsname, dev_buf, dev_buf_len) == 0)) {
|
||
if (core_id<0) {
|
||
cas_printf(LOG_ERR,
|
||
"Can't stop cache instance %d. Device %s is mounted!\n",
|
||
cache_id, mstruct->mnt_fsname);
|
||
} else {
|
||
cas_printf(LOG_ERR,
|
||
"Can't remove core %d from cache %d."
|
||
" Device %s is mounted!\n",
|
||
core_id, cache_id, mstruct->mnt_fsname);
|
||
}
|
||
return FAILURE;
|
||
}
|
||
}
|
||
return SUCCESS;
|
||
|
||
}
|
||
|
||
int remove_core(unsigned int cache_id, unsigned int core_id,
|
||
bool detach, bool force_no_flush)
|
||
{
|
||
int fd = 0;
|
||
struct kcas_remove_core cmd;
|
||
|
||
/* don't even attempt ioctl if filesystem is mounted */
|
||
if (SUCCESS != check_if_mounted(cache_id, core_id)) {
|
||
return FAILURE;
|
||
}
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
cmd.force_no_flush = force_no_flush;
|
||
cmd.detach = detach;
|
||
|
||
if (run_ioctl_interruptible(fd, KCAS_IOCTL_REMOVE_CORE, &cmd,
|
||
"Removing core", cache_id, core_id) < 0) {
|
||
close(fd);
|
||
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
|
||
cas_printf(LOG_ERR, "You have interrupted removal of core. CAS continues to operate normally.\n");
|
||
return INTERRUPTED;
|
||
} else {
|
||
cas_printf(LOG_ERR, "Error while removing core device %d from cache instance %d\n",
|
||
core_id, cache_id);
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
close(fd);
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int core_pool_remove(const char *core_device)
|
||
{
|
||
struct kcas_core_pool_remove cmd;
|
||
int fd;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
if (set_core_path(cmd.core_path_name, core_device, MAX_STR_LEN) != SUCCESS) {
|
||
cas_printf(LOG_ERR, "Failed to copy core path\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_CORE_POOL_REMOVE, &cmd) < 0) {
|
||
cas_printf(LOG_ERR, "Error while removing device %s from core pool\n",
|
||
core_device);
|
||
print_err(cmd.ext_err_code);
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
#define DIRTY_FLUSHING_WARNING "You have interrupted flushing of cache dirty data. CAS continues to operate\nnormally and dirty data that remains on cache device will be flushed by cleaning thread.\n"
|
||
int flush_cache(unsigned int cache_id)
|
||
{
|
||
int fd = 0;
|
||
struct kcas_flush_cache cmd;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
/* synchronous flag */
|
||
if (run_ioctl_interruptible(fd, KCAS_IOCTL_FLUSH_CACHE, &cmd, "Flushing cache",
|
||
cache_id, OCF_CORE_ID_INVALID) < 0) {
|
||
close(fd);
|
||
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
|
||
cas_printf(LOG_ERR, DIRTY_FLUSHING_WARNING);
|
||
return INTERRUPTED;
|
||
} else {
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
int flush_core(unsigned int cache_id, unsigned int core_id)
|
||
{
|
||
int fd = 0;
|
||
struct kcas_flush_core cmd;
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
/* synchronous flag */
|
||
if (run_ioctl_interruptible(fd, KCAS_IOCTL_FLUSH_CORE, &cmd, "Flushing core", cache_id, core_id) < 0) {
|
||
close(fd);
|
||
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
|
||
cas_printf(LOG_ERR, DIRTY_FLUSHING_WARNING);
|
||
return INTERRUPTED;
|
||
} else {
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
struct partition_config_col {
|
||
const char *name;
|
||
int pos;
|
||
};
|
||
|
||
static struct partition_config_col partition_config_columns[] = {
|
||
{ .name = "IO class id", .pos = -1 },
|
||
{ .name = "IO class name", .pos = -1 },
|
||
{ .name = "Eviction priority", .pos = -1 },
|
||
{ .name = "Allocation", .pos = -1 },
|
||
{ .name = NULL }
|
||
};
|
||
|
||
void partition_list_line(FILE *out, struct kcas_io_class *cls, bool csv)
|
||
{
|
||
char buffer[128];
|
||
const char *prio;
|
||
const char *allocation;
|
||
if (cls->info.cache_mode != ocf_cache_mode_pt)
|
||
allocation = csv ? "1" : "YES";
|
||
else
|
||
allocation = csv ? "0" : "NO";
|
||
|
||
if (OCF_IO_CLASS_PRIO_PINNED == cls->info.priority) {
|
||
prio = csv ? "" : "Pinned";
|
||
} else {
|
||
snprintf(buffer, sizeof(buffer), "%d", cls->info.priority);
|
||
prio = buffer;
|
||
}
|
||
|
||
fprintf(out, TAG(TABLE_ROW)"%u,%s,%s,%s\n",
|
||
cls->class_id, cls->info.name, prio, allocation);
|
||
|
||
}
|
||
|
||
int partition_list(unsigned int cache_id, unsigned int output_format)
|
||
{
|
||
struct kcas_io_class io_class = { .ext_err_code = 0 };
|
||
int fd, i = 0, result = 0;
|
||
/* 1 is writing end, 0 is reading end of a pipe */
|
||
FILE *intermediate_file[2];
|
||
bool use_csv, first_col;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1 )
|
||
return FAILURE;
|
||
|
||
if (create_pipe_pair(intermediate_file)) {
|
||
cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n");
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
use_csv = (output_format == OUTPUT_FORMAT_CSV);
|
||
|
||
first_col = true;
|
||
fprintf(intermediate_file[1], TAG(TABLE_HEADER));
|
||
for (i = 0; partition_config_columns[i].name; i++) {
|
||
if (!first_col) {
|
||
fputc(',', intermediate_file[1]);
|
||
}
|
||
fprintf(intermediate_file[1], "%s",
|
||
partition_config_columns[i].name);
|
||
first_col = false;
|
||
}
|
||
fputc('\n', intermediate_file[1]);
|
||
|
||
for (i = 0; i < OCF_IO_CLASS_MAX; i++, io_class.ext_err_code = 0) {
|
||
io_class.cache_id = cache_id;
|
||
io_class.class_id = i;
|
||
|
||
result = run_ioctl(fd, KCAS_IOCTL_PARTITION_STATS, &io_class);
|
||
if (result) {
|
||
if (OCF_ERR_IO_CLASS_NOT_EXIST == io_class.ext_err_code) {
|
||
result = SUCCESS;
|
||
continue;
|
||
} else {
|
||
result = FAILURE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
partition_list_line(intermediate_file[1],
|
||
&io_class, use_csv);
|
||
|
||
}
|
||
|
||
if (io_class.ext_err_code) {
|
||
print_err(io_class.ext_err_code);
|
||
}
|
||
|
||
fclose(intermediate_file[1]);
|
||
if (!result && stat_format_output(intermediate_file[0], stdout,
|
||
use_csv?RAW_CSV:TEXT)) {
|
||
cas_printf(LOG_ERR, "An error occured during statistics formatting.\n");
|
||
result = FAILURE;
|
||
}
|
||
fclose(intermediate_file[0]);
|
||
close(fd);
|
||
|
||
return result;
|
||
}
|
||
|
||
enum {
|
||
part_csv_coll_id = 0, part_csv_coll_name, part_csv_coll_prio,
|
||
part_csv_coll_alloc, part_csv_coll_max
|
||
};
|
||
|
||
int partition_is_name_valid(const char *name)
|
||
{
|
||
int i;
|
||
int length = strnlen(name, OCF_IO_CLASS_NAME_MAX);
|
||
if (0 == length || length >= OCF_IO_CLASS_NAME_MAX) {
|
||
cas_printf(LOG_ERR, "Empty or too long IO class name\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
for (i = 0; i < length; i++) {
|
||
if (name[i] == ',' || name[i] == '"' ||
|
||
name[i] < 32 || name[i] > 126) {
|
||
cas_printf(LOG_ERR, "Only characters allowed in IO "
|
||
"class name are low ascii characters, "
|
||
"excluding control characters, comma and "
|
||
"quotation mark.\n");
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
static inline const char *partition_get_csv_col(CSVFILE *csv, int col,
|
||
int *error_col)
|
||
{
|
||
const char *val;
|
||
|
||
val = csv_get_col(csv, partition_config_columns[col].pos);
|
||
if (!val) {
|
||
*error_col = col;
|
||
}
|
||
return val;
|
||
}
|
||
|
||
static inline int partition_get_line(CSVFILE *csv,
|
||
struct kcas_io_classes *cnfg,
|
||
int *error_col)
|
||
{
|
||
uint32_t part_id;
|
||
uint32_t value;
|
||
const char *id, *name, *prio, *alloc;
|
||
|
||
id = partition_get_csv_col(csv, part_csv_coll_id, error_col);
|
||
if (!id) {
|
||
return FAILURE;
|
||
}
|
||
name = partition_get_csv_col(csv, part_csv_coll_name, error_col);
|
||
if (!name) {
|
||
return FAILURE;
|
||
}
|
||
prio = partition_get_csv_col(csv, part_csv_coll_prio, error_col);
|
||
if (!prio) {
|
||
return FAILURE;
|
||
}
|
||
alloc = partition_get_csv_col(csv, part_csv_coll_alloc, error_col);
|
||
if (!alloc) {
|
||
return FAILURE;
|
||
}
|
||
|
||
/* Validate ID */
|
||
*error_col = part_csv_coll_id;
|
||
if (strempty(id)) {
|
||
return FAILURE;
|
||
}
|
||
if (validate_str_num(id, "id", 0, OCF_IO_CLASS_ID_MAX)) {
|
||
return FAILURE;
|
||
}
|
||
part_id = strtoul(id, NULL, 10);
|
||
if (part_id > OCF_IO_CLASS_ID_MAX) {
|
||
cas_printf(LOG_ERR, "Invalid partition id\n");
|
||
return FAILURE;
|
||
}
|
||
if (!strempty(cnfg->info[part_id].name)) {
|
||
cas_printf(LOG_ERR, "Double configuration for IO class id %u\n",
|
||
part_id);
|
||
return FAILURE;
|
||
}
|
||
|
||
/* Validate name */
|
||
*error_col = part_csv_coll_name;
|
||
if (SUCCESS != partition_is_name_valid(name)) {
|
||
return FAILURE;
|
||
}
|
||
strncpy_s(cnfg->info[part_id].name, sizeof(cnfg->info[part_id].name),
|
||
name, strnlen_s(name, sizeof(cnfg->info[part_id].name)));
|
||
|
||
/* Validate Priority*/
|
||
*error_col = part_csv_coll_prio;
|
||
if (strempty(prio)) {
|
||
value = OCF_IO_CLASS_PRIO_PINNED;
|
||
} else {
|
||
if (validate_str_num(prio, "prio", OCF_IO_CLASS_PRIO_HIGHEST,
|
||
OCF_IO_CLASS_PRIO_LOWEST)) {
|
||
return FAILURE;
|
||
}
|
||
value = strtoul(prio, NULL, 10);
|
||
}
|
||
cnfg->info[part_id].priority = value;
|
||
|
||
/* Validate Allocation */
|
||
*error_col = part_csv_coll_alloc;
|
||
if (strempty(alloc)) {
|
||
return FAILURE;
|
||
}
|
||
if (validate_str_num(alloc, "alloc", 0, 1)) {
|
||
return FAILURE;
|
||
}
|
||
value = strtoul(alloc, NULL, 10);
|
||
if (0 == value) {
|
||
cnfg->info[part_id].cache_mode = ocf_cache_mode_pt;
|
||
} else if (1 == value) {
|
||
cnfg->info[part_id].cache_mode = ocf_cache_mode_max;
|
||
} else {
|
||
return FAILURE;
|
||
}
|
||
|
||
cnfg->info[part_id].min_size = 0;
|
||
cnfg->info[part_id].max_size = UINT32_MAX;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int partition_parse_header(CSVFILE *csv)
|
||
{
|
||
int i, j, csv_cols;
|
||
const char *col_name;
|
||
|
||
csv_cols = csv_count_cols(csv);
|
||
for (i = 0; i < csv_cols; i++) {
|
||
col_name = csv_get_col(csv, i);
|
||
|
||
if (!col_name) {
|
||
cas_printf(LOG_ERR, "Cannot parse configuration file.\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
for (j = 0; partition_config_columns[j].name; j++) {
|
||
if (!strncmp(col_name, partition_config_columns[j].name, MAX_STR_LEN)) {
|
||
partition_config_columns[j].pos = i;
|
||
break;
|
||
}
|
||
}
|
||
if (!partition_config_columns[j].name) {
|
||
cas_printf(LOG_ERR,
|
||
"Cannot parse configuration file - unknown column \"%s\".\n",
|
||
col_name);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
for (i = 0; partition_config_columns[i].name; i++) {
|
||
if (partition_config_columns[i].pos < 0) {
|
||
cas_printf(LOG_ERR,
|
||
"Cannot parse configuration file - missing column \"%s\".\n",
|
||
partition_config_columns[i].name);
|
||
return FAILURE;
|
||
}
|
||
}
|
||
return SUCCESS;
|
||
}
|
||
|
||
int partition_get_config(CSVFILE *csv, struct kcas_io_classes *cnfg,
|
||
int cache_id)
|
||
{
|
||
int result = 0, count = 0;
|
||
int line = 1;
|
||
int error_col = -1;
|
||
|
||
cnfg->cache_id = cache_id;
|
||
|
||
/* before reading io class configuration check header */
|
||
if (csv_read(csv)) {
|
||
if (csv_feof(csv)) {
|
||
cas_printf(LOG_ERR,
|
||
"Empty IO Classes configuration file"
|
||
" supplied.\n");
|
||
return FAILURE;
|
||
} else {
|
||
cas_printf(LOG_ERR,
|
||
"I/O error occured while reading"
|
||
" IO Classes configuration file"
|
||
" supplied.\n");
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
if (partition_parse_header(csv)) {
|
||
cas_printf(LOG_ERR, "Failed to parse I/O classes"
|
||
" configuration file header. It is either"
|
||
" malformed or missing.\n"
|
||
"Please consult Admin Guide to check how"
|
||
" columns in configuration file should"
|
||
" be named.\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
/* check all lines of input */
|
||
while (!csv_feof(csv)) {
|
||
line++;
|
||
if (csv_read(csv)) {
|
||
if (csv_feof(csv)) {
|
||
break;
|
||
} else {
|
||
result = FAILURE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (part_csv_coll_max != csv_count_cols(csv)) {
|
||
if (csv_empty_line(csv)) {
|
||
continue;
|
||
} else {
|
||
result = FAILURE;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (partition_get_line(csv, cnfg, &error_col)) {
|
||
result = FAILURE;
|
||
break;
|
||
}
|
||
|
||
count++;
|
||
}
|
||
|
||
if (result) {
|
||
if (error_col >= 0) {
|
||
cas_printf(LOG_ERR,
|
||
"Cannot parse configuration file - error in line %d in column %d (%s).\n",
|
||
line,
|
||
partition_config_columns[error_col].pos+1,
|
||
partition_config_columns[error_col].name);
|
||
} else {
|
||
cas_printf(LOG_ERR, "Cannot parse configuration file - error in line %d.\n", line);
|
||
}
|
||
} else if (0 == count) {
|
||
result = FAILURE;
|
||
cas_printf(LOG_ERR, "Empty configuration file\n");
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
int partition_set_config(struct kcas_io_classes *cnfg)
|
||
{
|
||
int fd;
|
||
int result = 0;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
result = run_ioctl(fd, KCAS_IOCTL_PARTITION_SET, cnfg);
|
||
if (result) {
|
||
if (OCF_ERR_IO_CLASS_NOT_EXIST == cnfg->ext_err_code) {
|
||
result = SUCCESS;
|
||
} else {
|
||
print_err(cnfg->ext_err_code);
|
||
result = FAILURE;
|
||
}
|
||
}
|
||
|
||
close(fd);
|
||
return result;
|
||
}
|
||
|
||
int partition_setup(unsigned int cache_id, const char *file)
|
||
{
|
||
int result = 0;
|
||
CSVFILE *in;
|
||
struct kcas_io_classes *cnfg = calloc(1, KCAS_IO_CLASSES_SIZE);
|
||
|
||
if (!cnfg)
|
||
return FAILURE;
|
||
|
||
if (strempty(file)) {
|
||
cas_printf(LOG_ERR, "Invalid path of configuration file\n");
|
||
result = FAILURE;
|
||
goto exit;
|
||
}
|
||
|
||
if ('-'==file[0] && (!file[1])) {
|
||
/* configuration is supposed to be read from stdin. Setup
|
||
* a csv parser treating standard input as input file instead
|
||
* of opening a regular file */
|
||
in = csv_fopen(stdin);
|
||
} else {
|
||
/* read ioclass configuration from a regular file */
|
||
in = csv_open(file, "r");
|
||
}
|
||
if (NULL == in) {
|
||
cas_printf(LOG_ERR, "Cannot open configuration file %s\n",
|
||
file);
|
||
result = FAILURE;
|
||
goto exit;
|
||
}
|
||
|
||
if (0 == partition_get_config(in, cnfg, cache_id)) {
|
||
result = partition_set_config(cnfg);
|
||
} else {
|
||
result = FAILURE;
|
||
}
|
||
|
||
if ('-' == file[0] && (!file[1])) {
|
||
/* free assets allocated by CSV parser without actually
|
||
* closing a file */
|
||
csv_close_nu(in);
|
||
} else {
|
||
csv_close(in);
|
||
}
|
||
|
||
exit:
|
||
free(cnfg);
|
||
return result;
|
||
}
|
||
|
||
int reset_counters(unsigned int cache_id, unsigned int core_id)
|
||
{
|
||
struct kcas_reset_stats cmd;
|
||
int fd = 0;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
memset(&cmd, 0, sizeof(cmd));
|
||
cmd.cache_id = cache_id;
|
||
cmd.core_id = core_id;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_RESET_STATS, &cmd) < 0) {
|
||
close(fd);
|
||
cas_printf(LOG_ERR, "Error encountered while reseting counters\n");
|
||
print_err(cmd.ext_err_code);
|
||
return FAILURE;
|
||
}
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
int cas_module_version(char *buff, int size)
|
||
{
|
||
FILE *fd;
|
||
int n_read;
|
||
|
||
if (size <= 0 || size > MAX_STR_LEN) {
|
||
return FAILURE;
|
||
}
|
||
memset(buff, 0, size);
|
||
|
||
fd = fopen("/sys/module/cas_cache/version", "r");
|
||
if (!fd) {
|
||
return FAILURE;
|
||
}
|
||
|
||
n_read = fread(buff, 1, size, fd);
|
||
if (ferror(fd)) {
|
||
n_read = 0;
|
||
}
|
||
fclose(fd);
|
||
|
||
if (n_read > 0) {
|
||
buff[n_read - 1] = '\0';
|
||
return SUCCESS;
|
||
} else {
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
int disk_module_version(char *buff, int size)
|
||
{
|
||
FILE *fd;
|
||
int n_read;
|
||
|
||
if (size <= 0 || size > MAX_STR_LEN) {
|
||
return FAILURE;
|
||
}
|
||
|
||
fd = fopen("/sys/module/cas_disk/version", "r");
|
||
if (!fd) {
|
||
return FAILURE;
|
||
}
|
||
|
||
n_read = fread(buff, 1, size, fd);
|
||
if (ferror(fd)) {
|
||
n_read = 0;
|
||
}
|
||
fclose(fd);
|
||
|
||
if (n_read > 0) {
|
||
buff[n_read - 1] = '\0';
|
||
return SUCCESS;
|
||
} else {
|
||
return FAILURE;
|
||
}
|
||
}
|
||
|
||
float calculate_flush_progress(unsigned dirty, unsigned flushed)
|
||
{
|
||
unsigned total_dirty;
|
||
|
||
if (!flushed)
|
||
return 0;
|
||
|
||
total_dirty = dirty + flushed;
|
||
return total_dirty ? 100. * flushed / total_dirty : 100;
|
||
}
|
||
|
||
int get_flush_progress(int unsigned cache_id, float *progress)
|
||
{
|
||
struct kcas_cache_info cmd_info;
|
||
int fd = 0;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
memset(&cmd_info, 0, sizeof(cmd_info));
|
||
|
||
cmd_info.cache_id = cache_id;
|
||
if (ioctl(fd, KCAS_IOCTL_CACHE_INFO, &cmd_info) < 0) {
|
||
close(fd);
|
||
return FAILURE;
|
||
}
|
||
|
||
*progress = calculate_flush_progress(cmd_info.info.dirty,
|
||
cmd_info.info.flushed);
|
||
|
||
close(fd);
|
||
return SUCCESS;
|
||
}
|
||
|
||
struct list_printout_ctx
|
||
{
|
||
FILE *intermediate;
|
||
FILE *out;
|
||
int type;
|
||
int result;
|
||
};
|
||
|
||
void *list_printout(void *ctx)
|
||
{
|
||
struct list_printout_ctx *spc = ctx;
|
||
if (stat_format_output(spc->intermediate,
|
||
spc->out, spc->type)) {
|
||
cas_printf(LOG_ERR, "An error occured during statistics formatting.\n");
|
||
spc->result = FAILURE;
|
||
} else {
|
||
spc->result = SUCCESS;
|
||
}
|
||
|
||
return NULL;
|
||
}
|
||
|
||
int get_core_pool_count(int fd)
|
||
{
|
||
struct kcas_core_pool_count cmd;
|
||
|
||
if (ioctl(fd, KCAS_IOCTL_GET_CORE_POOL_COUNT, &cmd) < 0)
|
||
return 0;
|
||
|
||
return cmd.core_pool_count;
|
||
}
|
||
|
||
int get_core_pool_devices(struct kcas_core_pool_path *cmd)
|
||
{
|
||
int fd, status, result = SUCCESS;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
cmd->core_pool_count = get_core_pool_count(fd);
|
||
if (cmd->core_pool_count <= 0) {
|
||
goto error_out;
|
||
}
|
||
|
||
cmd->core_path_tab = malloc(cmd->core_pool_count * MAX_STR_LEN);
|
||
if (NULL == cmd->core_path_tab) {
|
||
cmd->core_pool_count = 0;
|
||
goto error_out;
|
||
}
|
||
|
||
if ((status = ioctl(fd, KCAS_IOCTL_GET_CORE_POOL_PATHS, cmd)) < 0) {
|
||
cas_printf(LOG_ERR, "Error while retrieving core pool list %d %d\n",
|
||
errno, status);
|
||
free(cmd->core_path_tab);
|
||
result = FAILURE;
|
||
goto error_out;
|
||
}
|
||
|
||
error_out:
|
||
close(fd);
|
||
return result;
|
||
}
|
||
|
||
int list_caches(unsigned int list_format)
|
||
{
|
||
struct cache_device **caches, *curr_cache;
|
||
struct kcas_core_pool_path core_pool_path_cmd = {0};
|
||
struct core_device *curr_core;
|
||
int caches_count, i, j;
|
||
/* 1 is writing end, 0 is reading end of a pipe */
|
||
FILE *intermediate_file[2];
|
||
int result = SUCCESS;
|
||
pthread_t thread;
|
||
struct list_printout_ctx printout_ctx;
|
||
|
||
caches = get_cache_devices(&caches_count);
|
||
if (caches_count < 0) {
|
||
cas_printf(LOG_INFO, "Error getting caches list\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
if (get_core_pool_devices(&core_pool_path_cmd)) {
|
||
free_cache_devices_list(caches, caches_count);
|
||
cas_printf(LOG_INFO, "Error getting cores in pool list\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
if (caches == NULL && !core_pool_path_cmd.core_pool_count) {
|
||
cas_printf(LOG_INFO, "No caches running\n");
|
||
return SUCCESS;
|
||
}
|
||
|
||
if (create_pipe_pair(intermediate_file)) {
|
||
cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n");
|
||
free(core_pool_path_cmd.core_path_tab);
|
||
free_cache_devices_list(caches, caches_count);
|
||
return FAILURE;
|
||
}
|
||
|
||
printout_ctx.intermediate = intermediate_file[0];
|
||
printout_ctx.out = stdout;
|
||
printout_ctx.type = (OUTPUT_FORMAT_CSV == list_format ? RAW_CSV : TEXT);
|
||
|
||
if (pthread_create(&thread, 0, list_printout, &printout_ctx)) {
|
||
cas_printf(LOG_ERR,"Failed to create thread.\n");
|
||
free(core_pool_path_cmd.core_path_tab);
|
||
free_cache_devices_list(caches, caches_count);
|
||
fclose(intermediate_file[0]);
|
||
fclose(intermediate_file[1]);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (caches_count || core_pool_path_cmd.core_pool_count) {
|
||
fprintf(intermediate_file[1],
|
||
TAG(TREE_HEADER)"%s,%s,%s,%s,%s,%s\n",
|
||
"type", "id", "disk", "status",
|
||
"write policy", "device");
|
||
}
|
||
|
||
if (core_pool_path_cmd.core_pool_count) {
|
||
fprintf(intermediate_file[1], TAG(TREE_BRANCH)
|
||
"%s,%s,%s,%s,%s,%s\n",
|
||
"core pool", /* type */
|
||
"-", /* id */
|
||
"-",
|
||
"-",
|
||
"-", /* write policy */
|
||
"-" /* device */);
|
||
for (i = 0; i < core_pool_path_cmd.core_pool_count; i++) {
|
||
char *core_path = core_pool_path_cmd.core_path_tab + (MAX_STR_LEN * i);
|
||
if (get_dev_path(core_path, core_path, MAX_STR_LEN)) {
|
||
cas_printf(LOG_WARNING, "WARNING: Can not resolve path to core. "
|
||
"By-id path will be shown for that core.\n");
|
||
}
|
||
fprintf(intermediate_file[1], TAG(TREE_LEAF)
|
||
"%s,%s,%s,%s,%s,%s\n",
|
||
"core", /* type */
|
||
"-", /* id */
|
||
core_path,
|
||
"Detached",
|
||
"-", /* write policy */
|
||
"-" /* device */);
|
||
}
|
||
}
|
||
|
||
for (i = 0; i < caches_count; ++i) {
|
||
curr_cache = caches[i];
|
||
|
||
char status_buf[CACHE_STATE_LENGHT];
|
||
const char *tmp_status;
|
||
char mode_string[12];
|
||
float cache_flush_prog;
|
||
float core_flush_prog;
|
||
|
||
get_dev_path(curr_cache->device, curr_cache->device, sizeof(curr_cache->device));
|
||
|
||
cache_flush_prog = calculate_flush_progress(curr_cache->dirty, curr_cache->flushed);
|
||
if (cache_flush_prog) {
|
||
snprintf(status_buf, sizeof(status_buf),
|
||
"%s (%3.1f %%)", "Flushing", cache_flush_prog);
|
||
tmp_status = status_buf;
|
||
snprintf(mode_string, sizeof(mode_string), "wb->%s",
|
||
cache_mode_to_name(curr_cache->mode));
|
||
} else {
|
||
tmp_status = get_cache_state_name(curr_cache->state);
|
||
snprintf(mode_string, sizeof(mode_string), "%s",
|
||
cache_mode_to_name(curr_cache->mode));
|
||
}
|
||
|
||
fprintf(intermediate_file[1], TAG(TREE_BRANCH)
|
||
"%s,%u,%s,%s,%s,%s\n",
|
||
"cache", /* type */
|
||
curr_cache->id, /* id */
|
||
curr_cache->device, /* device path */
|
||
tmp_status, /* cache status */
|
||
mode_string, /* write policy */
|
||
"-" /* device */);
|
||
|
||
for (j = 0; j < curr_cache->core_count; ++j) {
|
||
char* core_path;
|
||
|
||
curr_core = &curr_cache->cores[j];
|
||
core_path = curr_core->path;
|
||
|
||
core_flush_prog = calculate_flush_progress(curr_core->info.stats.dirty,
|
||
curr_core->info.stats.flushed);
|
||
|
||
if (!core_flush_prog && cache_flush_prog) {
|
||
core_flush_prog = curr_core->info.stats.dirty ? 0 : 100;
|
||
}
|
||
|
||
if (core_flush_prog || cache_flush_prog) {
|
||
snprintf(status_buf, CACHE_STATE_LENGHT,
|
||
"%s (%3.1f %%)", "Flushing", core_flush_prog);
|
||
tmp_status = status_buf;
|
||
} else {
|
||
tmp_status = get_core_state_name(curr_core->info.state);
|
||
}
|
||
|
||
fprintf(intermediate_file[1], TAG(TREE_LEAF)
|
||
"%s,%u,%s,%s,%s,/dev/cas%d-%d\n",
|
||
"core", /* type */
|
||
curr_core->id, /* id */
|
||
core_path, /* path to core*/
|
||
tmp_status, /* core status */
|
||
"-", /* write policy */
|
||
curr_cache->id, /* core id (part of path)*/
|
||
curr_core->id /* cache id (part of path)*/ );
|
||
}
|
||
}
|
||
|
||
free_cache_devices_list(caches, caches_count);
|
||
free(core_pool_path_cmd.core_path_tab);
|
||
|
||
fclose(intermediate_file[1]);
|
||
pthread_join(thread, 0);
|
||
if (printout_ctx.result) {
|
||
result = 1;
|
||
cas_printf(LOG_ERR, "An error occured during list formatting.\n");
|
||
|
||
}
|
||
fclose(intermediate_file[0]);
|
||
return result;
|
||
}
|
||
|
||
int _get_cas_capabilites(struct kcas_capabilites *caps, int quiet)
|
||
{
|
||
static bool retrieved = false;
|
||
static struct kcas_capabilites caps_buf;
|
||
int status = SUCCESS;
|
||
int ctrl_fd;
|
||
if (!retrieved) {
|
||
if (quiet) {
|
||
ctrl_fd = open_ctrl_device_quiet();
|
||
} else {
|
||
ctrl_fd = open_ctrl_device();
|
||
}
|
||
|
||
if (ctrl_fd < 0) {
|
||
if (!quiet)
|
||
print_err(KCAS_ERR_SYSTEM);
|
||
|
||
return FAILURE;
|
||
}
|
||
|
||
status = ioctl(ctrl_fd, KCAS_IOCTL_GET_CAPABILITIES, &caps_buf);
|
||
close(ctrl_fd);
|
||
|
||
if (status) {
|
||
return FAILURE;
|
||
}
|
||
retrieved = true;
|
||
}
|
||
|
||
memcpy_s(caps, sizeof(*caps), &caps_buf, sizeof(caps_buf));
|
||
return status;
|
||
}
|
||
|
||
int get_cas_capabilites_quiet(struct kcas_capabilites *caps)
|
||
{
|
||
return _get_cas_capabilites(caps, true);
|
||
}
|
||
|
||
int get_cas_capabilites(struct kcas_capabilites *caps)
|
||
{
|
||
return _get_cas_capabilites(caps, false);
|
||
}
|
||
|
||
int nvme_format(const char *device_path, int metadata_mode, int force)
|
||
{
|
||
struct kcas_nvme_format cmd_info;
|
||
int fd;
|
||
int result = 0;
|
||
|
||
strncpy_s(cmd_info.device_path_name,
|
||
sizeof(cmd_info.device_path_name), device_path,
|
||
strnlen_s(device_path, sizeof(cmd_info.device_path_name)));
|
||
|
||
switch (metadata_mode) {
|
||
case METADATA_MODE_NORMAL:
|
||
cmd_info.metadata_mode = CAS_METADATA_MODE_NORMAL;
|
||
break;
|
||
case METADATA_MODE_ATOMIC:
|
||
cmd_info.metadata_mode = CAS_METADATA_MODE_ATOMIC;
|
||
break;
|
||
default:
|
||
return FAILURE;
|
||
}
|
||
cmd_info.force = force;
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
/* Format NVMe */
|
||
result = run_ioctl(fd, KCAS_IOCTL_NVME_FORMAT, &cmd_info);
|
||
close(fd);
|
||
|
||
if (result) {
|
||
result = cmd_info.ext_err_code ? : KCAS_ERR_SYSTEM;
|
||
cas_printf(LOG_INFO, "Changing NVMe format failed!\n");
|
||
print_err(result);
|
||
return FAILURE;
|
||
}
|
||
|
||
cas_printf(LOG_INFO, "Changing NVMe format succeeded.\n"
|
||
"IMPORTANT: Reboot is required!\n");
|
||
|
||
return SUCCESS;
|
||
}
|
||
|
||
int _check_cache_device(const char *device_path,
|
||
struct kcas_cache_check_device *cmd_info)
|
||
{
|
||
int result, fd;
|
||
|
||
strncpy_s(cmd_info->path_name, sizeof(cmd_info->path_name), device_path,
|
||
strnlen_s(device_path, sizeof(cmd_info->path_name)));
|
||
|
||
fd = open_ctrl_device();
|
||
if (fd == -1)
|
||
return FAILURE;
|
||
|
||
result = run_ioctl(fd, KCAS_IOCTL_CACHE_CHECK_DEVICE, cmd_info);
|
||
|
||
close(fd);
|
||
|
||
return result;
|
||
}
|
||
|
||
int check_cache_device(const char *device_path)
|
||
{
|
||
struct kcas_cache_check_device cmd_info;
|
||
FILE *intermediate_file[2];
|
||
int result;
|
||
|
||
result = _check_cache_device(device_path, &cmd_info);
|
||
|
||
if (result) {
|
||
result = cmd_info.ext_err_code ? : KCAS_ERR_SYSTEM;
|
||
print_err(result);
|
||
return FAILURE;
|
||
}
|
||
|
||
if (create_pipe_pair(intermediate_file)) {
|
||
cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n");
|
||
return FAILURE;
|
||
}
|
||
|
||
fprintf(intermediate_file[1], TAG(TABLE_HEADER) "Is cache,Clean Shutdown,Cache dirty\n");
|
||
|
||
fprintf(intermediate_file[1], TAG(TABLE_ROW));
|
||
if (cmd_info.is_cache_device) {
|
||
fprintf(intermediate_file[1], "yes,%s,%s\n",
|
||
cmd_info.clean_shutdown ? "yes" : "no",
|
||
cmd_info.cache_dirty ? "yes" : "no");
|
||
} else {
|
||
fprintf(intermediate_file[1], "no,-,-\n");
|
||
}
|
||
|
||
fclose(intermediate_file[1]);
|
||
stat_format_output(intermediate_file[0], stdout, RAW_CSV);
|
||
fclose(intermediate_file[0]);
|
||
|
||
return SUCCESS;
|
||
}
|