open-cas-linux/casadm/cas_lib.c
Michal Mielewczyk c6f2371aea casadm: More specific warn for irresolvable cache
Signed-off-by: Michal Mielewczyk <michal.mielewczyk@huawei.com>
2025-03-18 09:15:13 +01:00

3144 lines
76 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright(c) 2012-2022 Intel Corporation
* Copyright(c) 2024-2025 Huawei Technologies
* SPDX-License-Identifier: BSD-3-Clause
*/
#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 "psort.h"
#include <libgen.h>
#include <regex.h>
#include <math.h>
#define PRINT_STAT(x) header->cmd_input.cache_stats.x
#define CORE_ADD_MAX_TIMEOUT 30
bool device_mounts_detected(const char *pattern, int cmplen);
void print_mounted_devices(const char *pattern, int cmplen);
/* 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_detached] = "Detached",
[ocf_cache_state_incomplete] = "Incomplete",
[ocf_cache_state_standby] = "Standby",
[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 STANDBY_DETACHED_STATE "Standby detached"
#define CACHE_STATE_LENGTH 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;
}
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;
struct stat status;
fstab_entry = getfsspec(dev_path);
if (fstab_entry != NULL) {
printf("Device entry present in fstab, please remove it.\n");
return FAILURE;
}
if (stat(dev_path, &status) == -1) {
printf("Failed to query device status.\n");
return FAILURE;
}
if (!S_ISBLK(status.st_mode)) {
printf("Path does not describe a block device\n");
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 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
{ .short_name = "wo", .long_name = "Write-Only", .value = ocf_cache_mode_wo },
{ 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 promotion_policy_names[] = {
{ .short_name = "always", .value = ocf_promotion_always },
{ .short_name = "nhit", .value = ocf_promotion_nhit },
{ 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 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;
}
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");
}
inline int validate_str_promotion_policy(const char *s)
{
return validate_str_val_mapping(s, promotion_policy_names, -1);
}
inline const char *promotion_policy_to_name(uint8_t policy)
{
return val_to_short_name(policy, promotion_policy_names, "Unknown");
}
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);
}
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, bool detached)
{
int i;
if (detached == true)
return STANDBY_DETACHED_STATE;
/* 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];
}
/**
* Save to dest an absolute device file path of src.
* Return number of characters copied to dest if succeed, negative value if failed.
*/
static int get_abs_path(char* dest, size_t dest_len, const char* src, size_t src_len)
{
int path_len = -FAILURE;
char *dir_name, *dev_name;
char *dev = strndup(src, src_len); // strdup creates hidden malloc
if (!dev) // basename/dirname may modify the source and
goto dev_err; // segfault when called with a static string
char *dir = strndup(src, src_len);
if (!dir)
goto dir_err;
dir_name = realpath(dirname(dir), NULL); // realpath creates hidden malloc
if (!dir_name)
goto dir_name_err;
dev_name = basename(dev);
if (!dev_name)
goto dev_name_err;
path_len = snprintf(dest, dest_len, "%s/%s", dir_name, dev_name);
dev_name_err:
free(dir_name);
dir_name_err:
free(dir);
dir_err:
free(dev);
dev_err:
return path_len;
}
/**
* @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;
}
/* Indicate whether given path should be passed without check */
static bool is_dev_link_whitelisted(const char* path)
{
regex_t regex;
int result;
static const char* const whitelist[] = {
"/dev/cas[0-9]\\+-[0-9]\\+$",
"/dev/cas[0-9]\\+-[0-9]\\+p[0-9]\\+$",
"/dev/ram[0-9]\\+$",
"/dev/ram[0-9]\\+p[0-9]\\+$",
"/dev/nullb[0-9]\\+$",
"/dev/drbd[0-9]\\+$",
"/dev/drbd[0-9]\\+p[0-9]\\+$",
};
static const unsigned count = ARRAY_SIZE(whitelist);
size_t i;
for (i = 0; i < count; i++) {
result = regcomp(&regex, whitelist[i], REG_NOSUB);
if (result)
return FAILURE;
result = regexec(&regex, path, 0, NULL, 0);
regfree(&regex);
if (!result) {
return true;
}
}
return false;
}
/* Call this only AFTER normalizing path */
static int _is_by_id_path(const char* dev_path)
{
static const char dev_by_id_dir[] = "/dev/disk/by-id";
return (!strncmp(dev_path, dev_by_id_dir,
strnlen_s(dev_by_id_dir, sizeof(dev_by_id_dir))));
}
int set_device_path(char *dest_path, size_t dest_len, const char *src_path, size_t src_len)
{
char abs_dev_path[MAX_STR_LEN];
int result;
/* save given path as absolute path in temporary variable */
if (get_abs_path(abs_dev_path, sizeof(abs_dev_path), src_path, src_len) < 0) {
cas_printf(LOG_ERR, "Failed to resolve path.\n");
return FAILURE;
}
/* check if given dev_path is whitelisted and then pass it as path or not */
if (is_dev_link_whitelisted(abs_dev_path)){
result = strncpy_s(dest_path, dest_len, abs_dev_path, sizeof(abs_dev_path));
return result ?: SUCCESS;
}
if (_is_by_id_path(abs_dev_path)) {
result = strncpy_s(dest_path, dest_len, abs_dev_path,
strnlen_s(abs_dev_path, sizeof(abs_dev_path)));
if (!result)
return SUCCESS;
else
cas_printf(LOG_ERR, "Internal error copying device path\n");
}
cas_printf(LOG_ERR, "Please use correct by-id path to the device %s.\n",
src_path);
return FAILURE;
}
int get_core_info(int fd, int cache_id, int core_id,
struct kcas_core_info *info, bool by_id_path)
{
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;
}
if (!by_id_path) {
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, bool by_id_path)
{
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, by_id_path)) {
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, bool by_id_path)
{
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;
cache->standby_detached = info->info.standby_detached;
if (strncpy_s(cache->device, sizeof(cache->device),
info->cache_path_name,
sizeof(info->cache_path_name))) {
free(cache);
return NULL;
}
cache->mode = info->info.cache_mode;
cache->dirty = info->info.dirty;
cache->flushed = info->info.flushed;
cache->cleaning_policy = info->info.cleaning_policy;
cache->promotion_policy = info->info.promotion_policy;
cache->size = info->info.cache_line_size;
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, by_id_path);
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, bool by_id_path)
{
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, by_id_path);
}
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, bool by_id_path)
{
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, by_id_path)) == 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, false);
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 int _verify_and_parse_volume_path(char *tgt_buf,
size_t tgt_buf_size, const char *cache_device,
size_t paths_size)
{
int fd = 0;
/* check if cache device 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 (set_device_path(tgt_buf, tgt_buf_size, cache_device, paths_size) != SUCCESS) {
return FAILURE;
}
return SUCCESS;
}
static int _start_cache(uint16_t cache_id, unsigned int cache_init,
const char *cache_device, ocf_cache_mode_t cache_mode,
ocf_cache_line_size_t line_size, int force, bool start)
{
int fd = 0;
struct kcas_start_cache cmd = {};
int status;
int ioctl = start ? KCAS_IOCTL_START_CACHE : KCAS_IOCTL_ATTACH_CACHE;
double min_free_ram_gb;
fd = open_ctrl_device();
if (fd == -1)
return FAILURE;
status = _verify_and_parse_volume_path(
cmd.cache_path_name,
sizeof(cmd.cache_path_name),
cache_device,
MAX_STR_LEN);
if (status != SUCCESS) {
close(fd);
return FAILURE;
}
cmd.cache_id = cache_id;
cmd.caching_mode = cache_mode;
cmd.line_size = line_size;
cmd.force = (uint8_t)force;
cmd.init_cache = cache_init;
status = run_ioctl_interruptible_retry(
fd,
ioctl,
&cmd,
start ? "Starting cache" : "Attaching device to cache",
cache_id,
OCF_CORE_ID_INVALID);
cache_id = cmd.cache_id;
if (status < 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.2fGB to %s cache"
" with cache line size equal %llukB.\n",
min_free_ram_gb,
start ? "start" : "attach a device to",
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 (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);
if (OCF_ERR_METADATA_FOUND == cmd.ext_err_code) {
/* print instructions specific for start/attach */
if (start) {
cas_printf(LOG_ERR,
"Please load cache metadata using --load"
" option or use --force to\n discard on-disk"
" metadata and start fresh cache instance.\n"
);
} else {
cas_printf(LOG_ERR,
"Please attach another device or use --force"
" to discard on-disk metadata\n"
" and attach this device to cache instance.\n"
);
}
}
}
return FAILURE;
}
}
check_cache_state_incomplete(cache_id, fd);
close(fd);
cas_printf(LOG_INFO, "Successfully %s %u\n",
start ? "added cache instance" : "attached device to cache",
cache_id);
return SUCCESS;
}
int start_cache(uint16_t cache_id, unsigned int cache_init,
const char *cache_device, ocf_cache_mode_t cache_mode,
ocf_cache_line_size_t line_size, int force)
{
return _start_cache(cache_id, cache_init, cache_device, cache_mode,
line_size, force, true);
}
int attach_cache(uint16_t cache_id, const char *cache_device, int force)
{
return _start_cache(cache_id, CACHE_INIT_NEW, cache_device,
ocf_cache_mode_none, ocf_cache_line_size_none, force, false);
}
int detach_cache(uint16_t cache_id)
{
int fd = 0;
struct kcas_stop_cache cmd = {};
int ioctl_code = KCAS_IOCTL_DETACH_CACHE;
int status;
fd = open_ctrl_device();
if (fd == -1)
return FAILURE;
cmd.cache_id = cache_id;
cmd.flush_data = true;
status = run_ioctl_interruptible_retry(
fd,
ioctl_code,
&cmd,
"Detaching the device from cache",
cache_id,
OCF_CORE_ID_INVALID);
close(fd);
if (status < 0) {
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
cas_printf(LOG_ERR,
"You have interrupted detaching the device "
"from cache %d. CAS continues to operate "
"normally.\n",
cache_id
);
return INTERRUPTED;
} else if (OCF_ERR_WRITE_CACHE == cmd.ext_err_code) {
cas_printf(LOG_ERR,
"Detached the device from cache %d "
"with errors\n",
cache_id
);
print_err(cmd.ext_err_code);
return FAILURE;
} else {
cas_printf(LOG_ERR,
"Error while detaching the device from"
" cache %d\n",
cache_id
);
print_err(cmd.ext_err_code);
return FAILURE;
}
}
cas_printf(LOG_INFO, "Successfully detached device from cache %hu\n",
cache_id);
return SUCCESS;
}
int stop_cache(uint16_t cache_id, int flush)
{
int fd = 0;
struct kcas_stop_cache cmd = {};
int ioctl_code = KCAS_IOCTL_STOP_CACHE;
int status;
/* Don't stop instance with mounted filesystem */
int cmplen = 0;
char pattern[80];
/* verify if any core (or core partition) for this cache is mounted */
cmplen = snprintf(pattern, sizeof(pattern), "/dev/cas%d-", cache_id) - 1;
if (device_mounts_detected(pattern, cmplen)) {
cas_printf(LOG_ERR, "Can't stop cache instance %d due to mounted devices:\n", cache_id);
print_mounted_devices(pattern, cmplen);
return FAILURE;
}
fd = open_ctrl_device();
if (fd == -1)
return FAILURE;
cmd.cache_id = cache_id;
cmd.flush_data = flush;
status = run_ioctl_interruptible_retry(
fd,
ioctl_code,
&cmd,
"Stopping cache",
cache_id,
OCF_CORE_ID_INVALID);
close(fd);
if (status < 0) {
if (OCF_ERR_FLUSHING_INTERRUPTED == cmd.ext_err_code) {
cas_printf(LOG_ERR,
"You have interrupted stopping of cache %d. "
"CAS continues\nto operate normally. The cache"
" can be stopped without\nflushing dirty data "
"by using '-n' option.\n", cache_id);
return INTERRUPTED;
} else if (OCF_ERR_WRITE_CACHE == cmd.ext_err_code){
cas_printf(LOG_ERR, "Stopped cache %d with errors\n", cache_id);
print_err(cmd.ext_err_code);
return FAILURE;
} else {
cas_printf(LOG_ERR, "Error while stopping cache %d\n", cache_id);
print_err(cmd.ext_err_code);
return FAILURE;
}
}
cas_printf(LOG_INFO, "Successfully stopped cache %hu\n", cache_id);
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)
{
if (cmd_info.ext_err_code == OCF_ERR_CACHE_NOT_EXIST)
print_err(cmd_info.ext_err_code);
if (cmd_info.ext_err_code == OCF_ERR_CACHE_STANDBY)
print_err(cmd_info.ext_err_code);
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;
bool flush_param_required;
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 unless we're transitioning
* out of lazy write cache mode (like WB or WO), in which case user must explicitly
* state his preference */
flush_param_required = ocf_mngt_cache_mode_has_lazy_write(orig_mode) &&
!ocf_mngt_cache_mode_has_lazy_write(cache_mode);
if (-1 == flush) {
if (flush_param_required) {
cas_printf(LOG_ERR, "Error: Required parameter (--flush-cache) was not specified.\n");
close(fd);
return FAILURE;
} else {
flush=NO;
}
}
if (flush_param_required) {
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 %s to %s mode.\n"
"Dirty data are being flushed to primary storage device in background.\n"
"Please find flushing progress via statistics command (casadm -P).\n",
cache_mode_to_name_long(orig_mode),
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_retry(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 if (OCF_ERR_CACHE_STANDBY == cmd.ext_err_code) {
print_err(cmd.ext_err_code);
return FAILURE;
} 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);
if (cmd.ext_err_code == OCF_ERR_CACHE_STANDBY) {
print_err(cmd.ext_err_code);
}
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 {
print_err(cmd.ext_err_code);
}
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], &params[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) {
if (cmd.ext_err_code == OCF_ERR_CACHE_STANDBY)
print_err(cmd.ext_err_code);
close(fd);
return FAILURE;
}
}
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 if (cmd.ext_err_code == OCF_ERR_CACHE_STANDBY)
print_err(cmd.ext_err_code);
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], &params[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, false);
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 (curr_core->info.state == ocf_core_state_active && 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, false);
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, false);
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);
}
}
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 */
if (try_add && core_id == OCF_CORE_ID_INVALID) {
cas_printf(LOG_ERR, "Option '--core-id' is missing\n");
return FAILURE;
}
/* 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_device_path(cmd.core_path_name, sizeof(cmd.core_path_name),
core_device, MAX_STR_LEN) != SUCCESS)
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 recursive 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;
}
bool device_mounts_detected(const char *pattern, int cmplen)
{
FILE *mtab;
struct mntent *mstruct;
int no_match = 0, error = 0;
mtab = setmntent("/etc/mtab", "r");
if (!mtab) {
/* if /etc/mtab not found then the kernel will check for mounts */
return false;
}
while ((mstruct = getmntent(mtab)) != NULL) {
error = strcmp_s(mstruct->mnt_fsname, cmplen, pattern, &no_match);
/* mstruct->mnt_fsname is /dev/... block device path, not a mountpoint */
if (error != EOK)
return false;
if (no_match)
continue;
return true;
}
return false;
}
void print_mounted_devices(const char *pattern, int cmplen)
{
FILE *mtab;
struct mntent *mstruct;
int no_match = 0, error = 0;
mtab = setmntent("/etc/mtab", "r");
if (!mtab) {
/* should exist, but if /etc/mtab not found we cannot print mounted devices */
return;
}
while ((mstruct = getmntent(mtab)) != NULL) {
error = strcmp_s(mstruct->mnt_fsname, cmplen, pattern, &no_match);
/* mstruct->mnt_fsname is /dev/... block device path, not a mountpoint */
if (error != EOK || no_match)
continue;
cas_printf(LOG_ERR, "%s\n", mstruct->mnt_fsname);
}
}
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 */
bool mounts_detected = false;
int cmplen = 0;
char pattern[80];
/* verify if specific core is mounted */
cmplen = snprintf(pattern, sizeof(pattern), "/dev/cas%d-%d", cache_id, core_id);
mounts_detected = device_mounts_detected(pattern, cmplen);
if (!mounts_detected) {
/* verify if any partition of the core is mounted */
cmplen = snprintf(pattern, sizeof(pattern), "/dev/cas%d-%dp", cache_id, core_id) - 1;
mounts_detected = device_mounts_detected(pattern, cmplen);
}
if (mounts_detected) {
cas_printf(LOG_ERR, "Can't remove core %d from "
"cache %d due to mounted devices:\n",
core_id, cache_id);
print_mounted_devices(pattern, cmplen);
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,
cmd.detach?"Detaching core":"Removing core", cache_id, core_id) < 0) {
close(fd);
if (cmd.ext_err_code == OCF_ERR_FLUSHING_INTERRUPTED) {
cas_printf(LOG_ERR, "You have interrupted %s of core. "
"CAS continues to operate normally.\n",
detach ? "detaching" : "removal");
return INTERRUPTED;
} else if (cmd.ext_err_code == OCF_ERR_CORE_IN_INACTIVE_STATE) {
cas_printf(LOG_ERR, "Core is inactive. To manage the "
"inactive core use '--remove-inactive' "
"command.\n");
return FAILURE;
} else if (cmd.ext_err_code == KCAS_ERR_DETACHED) {
print_err(cmd.ext_err_code);
return FAILURE;
} else {
cas_printf(LOG_ERR, "Error while %s core device %d "
"from cache instance %d\n",
detach ? "detaching" : "removing",
core_id, cache_id);
print_err(cmd.ext_err_code);
return FAILURE;
}
}
close(fd);
return SUCCESS;
}
void check_cache_state_incomplete(int cache_id, int fd) {
struct cache_device *cache =
get_cache_device_by_id_fd(cache_id, fd, false);
if (cache == NULL)
return;
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");
}
free(cache);
}
int remove_inactive_core(unsigned int cache_id, unsigned int core_id,
bool force)
{
int fd = 0;
struct kcas_remove_inactive cmd;
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 = force;
if (run_ioctl(fd, KCAS_IOCTL_REMOVE_INACTIVE, &cmd) < 0) {
close(fd);
if (cmd.ext_err_code == KCAS_ERR_CORE_IN_ACTIVE_STATE) {
cas_printf(LOG_ERR, "Core is active. "
"To manage the active core use "
"'--remove-core' command.\n");
} else {
cas_printf(LOG_ERR, "Error while removing inactive "
"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_device_path(cmd.core_path_name, sizeof(cmd.core_path_name),
core_device, MAX_STR_LEN) != SUCCESS) {
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;
}
int purge_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_PURGE_CACHE, &cmd, "Purging cache",
cache_id, OCF_CORE_ID_INVALID) < 0) {
close(fd);
print_err(cmd.ext_err_code);
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_retry(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 purge_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_PURGE_CORE, &cmd, "Purging core", cache_id, core_id) < 0) {
close(fd);
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_retry(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;
char allocation_str[MAX_STR_LEN];
snprintf(allocation_str, sizeof(allocation_str), "%d.%02d",
cls->info.max_size/100, cls->info.max_size%100);
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_str);
}
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_USER_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_INFO, &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 occurred 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 int calculate_max_allocation(uint16_t cache_id, const char *allocation,
uint32_t *part_size)
{
float alloc = 0;
char *end;
if (strnlen(allocation, MAX_STR_LEN) > 4)
return FAILURE;
alloc = strtof(allocation, &end);
if (alloc > 1 || alloc < 0)
return FAILURE;
if (allocation + strnlen(allocation, MAX_STR_LEN) != end)
return FAILURE;
*part_size = (uint32_t)round(alloc * 100);
return SUCCESS;
}
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)));
if (0 == part_id && strcmp(name, "unclassified")) {
cas_printf(LOG_ERR, "IO class 0 must have the default name 'unclassified'\n");
return FAILURE;
}
/* 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 (calculate_max_allocation(cnfg->cache_id, alloc, &value) == FAILURE)
return FAILURE;
cnfg->info[part_id].cache_mode = ocf_cache_mode_max;
cnfg->info[part_id].min_size = 0;
cnfg->info[part_id].max_size = value;
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 occurred 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 if (OCF_ERR_CACHE_STANDBY == cnfg->ext_err_code) {
print_err(cnfg->ext_err_code);
result = FAILURE;
} 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) {
if (OCF_ERR_CACHE_STANDBY == cmd.ext_err_code) {
print_err(cmd.ext_err_code);
} else {
cas_printf(LOG_ERR, "Error encountered while resetting counters\n");
print_err(cmd.ext_err_code);
}
close(fd);
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;
}
}
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 occurred 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, bool by_id_path)
{
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, by_id_path);
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 (!by_id_path) {
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_LENGTH];
const char *tmp_status;
char mode_string[12];
char exp_obj[32];
char cache_ctrl_dev[MAX_STR_LEN] = "-";
float cache_flush_prog;
float core_flush_prog;
bool cache_device_detached =
((curr_cache->state & (1 << ocf_cache_state_standby)) |
(curr_cache->state & (1 << ocf_cache_state_detached)));
if (!by_id_path && !cache_device_detached) {
if (get_dev_path(curr_cache->device, curr_cache->device,
sizeof(curr_cache->device))) {
cas_printf(LOG_WARNING,
"WARNING: Cannot resolve path to "
"cache %d. By-id path will be shown "
"for that cache.\n", curr_cache->id);
}
}
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, curr_cache->standby_detached);
if (curr_cache->state & (1 << ocf_cache_state_standby)) {
strncpy(mode_string, "-", sizeof(mode_string));
if (!curr_cache->standby_detached) {
snprintf(cache_ctrl_dev, sizeof(cache_ctrl_dev),
"/dev/cas-cache-%d", curr_cache->id);
}
} else {
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 */
cache_device_detached ? "-" : curr_cache->device, /* device path */
tmp_status, /* cache status */
mode_string, /* write policy */
cache_ctrl_dev /* 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.info.dirty,
curr_core->info.info.flushed);
if (!core_flush_prog && cache_flush_prog) {
core_flush_prog = curr_core->info.info.dirty ? 0 : 100;
}
if (core_flush_prog || cache_flush_prog) {
snprintf(status_buf, CACHE_STATE_LENGTH,
"%s (%3.1f %%)", "Flushing", core_flush_prog);
tmp_status = status_buf;
} else {
tmp_status = get_core_state_name(curr_core->info.state);
}
snprintf(exp_obj, sizeof(exp_obj), "/dev/cas%d-%d",
curr_cache->id, curr_core->id);
fprintf(intermediate_file[1], TAG(TREE_LEAF)
"%s,%u,%s,%s,%s,%s\n",
"core", /* type */
curr_core->id, /* id */
core_path, /* path to core*/
tmp_status, /* core status */
"-", /* write policy */
curr_core->info.exp_obj_exists ? exp_obj : "-" /* exported object 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 occurred during list formatting.\n");
}
fclose(intermediate_file[0]);
return result;
}
int _check_cache_device(const char *device_path,
struct kcas_cache_check_device *cmd_info)
{
int result, fd;
if (strncpy_s(cmd_info->path_name, sizeof(cmd_info->path_name),
device_path, MAX_STR_LEN)) {
return FAILURE;
}
fd = open_ctrl_device();
if (fd == -1)
return FAILURE;
result = run_ioctl(fd, KCAS_IOCTL_CACHE_CHECK_DEVICE, cmd_info);
close(fd);
return result ? FAILURE : SUCCESS;
}
int check_cache_device(const char *device_path)
{
struct kcas_cache_check_device cmd_info = {};
FILE *intermediate_file[2];
int result;
if (set_device_path(cmd_info.path_name, sizeof(cmd_info.path_name),
device_path, MAX_STR_LEN) != SUCCESS)
return FAILURE;
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 && cmd_info.metadata_compatible) {
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;
}
int zero_md(const char *cache_device, bool force)
{
struct kcas_cache_check_device cmd_info = {};
char zero_page[4096] = {0};
int fd = 0;
int result;
/* check if device is available */
fd = open(cache_device, O_WRONLY | O_SYNC | O_EXCL);
if (fd < 0) {
cas_printf(LOG_ERR, "Error while opening '%s'exclusively. This can be due to\n"
"cache instance running on this device. In such case please "
"stop the cache and try again.\n", cache_device);
return FAILURE;
}
close(fd);
result = _check_cache_device(cache_device, &cmd_info);
if (result == FAILURE) {
cas_printf(LOG_ERR, "Failed to retrieve device's information.\n");
return FAILURE;
}
/* don't delete metadata if device hasn't got CAS's metadata */
if (!cmd_info.is_cache_device) {
cas_printf(LOG_ERR, "Device '%s' does not contain OpenCAS's metadata.\n", cache_device);
return FAILURE;
}
if (!cmd_info.clean_shutdown) {
if (!force) {
cas_printf(LOG_ERR, "Cache instance did not shut down cleanly. It might contain dirty data. \n"
"Clearing metadata might result in loss of dirty data. Please recover cache instance\n"
"by loading it and flush dirty data in order to preserve them on the core device.\n"
"Alternatively, if you wish to clear metadata anyway, please use '--force' option. \n");
return FAILURE;
} else {
cas_printf(LOG_WARNING, "Clearing metadata after dirty shutdown - potential loss of dirty data.\n");
}
} else if (cmd_info.cache_dirty) {
if (!force) {
cas_printf(LOG_ERR, "Cache instance contains dirty data. Clearing metadata will result in loss of dirty data.\n"
"Please load cache instance and flush dirty data in order to preserve them on the core device.\n"
"Alternatively, if you wish to clear metadata anyway, please use '--force' option. \n");
return FAILURE;
} else {
cas_printf(LOG_WARNING, "Clearing metadata for dirty pages - dirty cache data is being discarded. \n");
}
}
fd = open(cache_device, O_WRONLY | O_SYNC | O_EXCL);
if (fd < 0) {
cas_printf(LOG_ERR, "Error while opening '%s'exclusively. This can be due to\n"
"cache instance running on this device. In such case please\n"
"stop the cache and try again.\n", cache_device);
return FAILURE;
}
if(write(fd, zero_page, 4096) != 4096) {
close(fd);
cas_printf(LOG_ERR, "Error while wiping out metadata from device '%s'.\n", cache_device);
return FAILURE;
}
close(fd);
cas_printf(LOG_INFO, "OpenCAS's metadata wiped successfully from device '%s'.\n", cache_device);
return SUCCESS;
}
int cas_ioctl(int id, void *data)
{
int fd, result;
fd = open_ctrl_device();
if (fd == -1)
return FAILURE;
result = run_ioctl(fd, id, data);
close(fd);
return result < 0 ? FAILURE : SUCCESS;
}
int standby_init(int cache_id, ocf_cache_line_size_t line_size,
const char *cache_device, int force)
{
return start_cache(cache_id,
CACHE_INIT_STANDBY_NEW,
cache_device,
ocf_cache_mode_default,
line_size,
force);
}
int standby_load(int cache_id, ocf_cache_line_size_t line_size,
const char *cache_device)
{
return start_cache(cache_id,
CACHE_INIT_STANDBY_LOAD,
cache_device,
ocf_cache_mode_none,
line_size,
0);
}
int standby_detach(int cache_id)
{
struct kcas_standby_detach cmd = {
.cache_id = cache_id
};
if (cas_ioctl(KCAS_IOCTL_STANDBY_DETACH, &cmd) != SUCCESS) {
print_err(cmd.ext_err_code ? : KCAS_ERR_SYSTEM);
return FAILURE;
}
cas_printf(LOG_INFO, "Successfully detached cache instance %hu\n",
cache_id);
return SUCCESS;
}
int standby_activate(int cache_id, const char *cache_device)
{
int fd = 0;
struct kcas_standby_activate cmd = {
.cache_id = cache_id
};
if (set_device_path(cmd.cache_path, sizeof(cmd.cache_path),
cache_device, MAX_STR_LEN) != SUCCESS) {
return FAILURE;
}
if (cas_ioctl(KCAS_IOCTL_STANDBY_ACTIVATE, &cmd) != SUCCESS) {
cas_printf(LOG_ERR, "Error activating cache %d\n", cache_id);
if (abs(cmd.ext_err_code) == OCF_ERR_NOT_OPEN_EXC) {
cas_printf(LOG_ERR, "Cannot open the device exclusively. Make sure "
"to detach cache before activation.\n");
} else {
print_err(cmd.ext_err_code ? : KCAS_ERR_SYSTEM);
}
return FAILURE;
}
fd = open_ctrl_device();
if(fd == -1)
return FAILURE;
check_cache_state_incomplete(cache_id, fd);
close(fd);
cas_printf(LOG_INFO, "Successfully activated cache instance %hu\n",
cache_id);
return SUCCESS;
}