/* * Copyright(c) 2012-2021 Intel Corporation * SPDX-License-Identifier: BSD-3-Clause-Clear */ #include "cas_cache.h" #define INTERNAL_CALL 0 #define EXTERNAL_CALL 1 #define CAS_PROPERTIES_VERSION 101 #define VERSION_STR ".version" /* * Difference between constant and non constant entry is store in LSB * e.g.: * cas_property_string in binary 0000 1010 * cas_property_string_const in binary 0000 1011 */ #define CAS_PROP_UNCONST(type) (type & ~CAS_PROPERTIES_CONST) #define CAS_PROP_CHECK_CONST(type) (type & CAS_PROPERTIES_CONST) enum cas_property_type { cas_property_string = 10, cas_property_string_const = (cas_property_string | CAS_PROPERTIES_CONST), cas_property_sint = 16, cas_property_sint_const = (cas_property_sint | CAS_PROPERTIES_CONST), cas_property_uint = 74, cas_property_uint_const = (cas_property_uint | CAS_PROPERTIES_CONST), }; struct cas_properties { struct list_head list; }; struct _cas_property { uint8_t type; char *key; struct list_head item; union { void *value; uint64_t value_uint; int64_t value_sint; }; }; struct cas_properties *cas_properties_create(void) { struct cas_properties *props; int result; props = kzalloc(sizeof(*props), GFP_KERNEL); if (!props) return ERR_PTR(-ENOMEM); INIT_LIST_HEAD(&props->list); result = cas_properties_add_uint(props, VERSION_STR, CAS_PROPERTIES_VERSION, CAS_PROPERTIES_CONST); if (result) { kfree(props); return ERR_PTR(result); } result = cas_properties_add_uint(props, ".size", 0, CAS_PROPERTIES_NON_CONST); if (result) { kfree(props); return ERR_PTR(result); } return props; } void cas_properties_destroy(struct cas_properties *props) { struct list_head *curr, *tmp; struct _cas_property *entry; list_for_each_safe(curr, tmp, &props->list) { entry = list_entry(curr, struct _cas_property, item); list_del(curr); if (cas_property_string == CAS_PROP_UNCONST(entry->type)) kfree(entry->value); kfree(entry->key); kfree(entry); } kfree(props); } static uint64_t _cas_prop_get_size(struct cas_properties *props) { struct list_head *curr; struct _cas_property *entry; uint64_t size_all = 0; list_for_each(curr, &props->list) { entry = list_entry(curr, struct _cas_property, item); size_all += cas_prop_strnlen(entry->key, MAX_STRING_SIZE) + 1; size_all += sizeof(entry->type); switch (CAS_PROP_UNCONST(entry->type)) { case cas_property_string: size_all += cas_prop_strnlen(entry->value, MAX_STRING_SIZE) + 1; break; case cas_property_sint: size_all += sizeof(entry->value_sint); break; case cas_property_uint: size_all += sizeof(entry->value_uint); break; default: return 0; } } return size_all; } static int _cas_prop_serialize_string(char *buffer, const uint64_t size, uint64_t *offset, char *value) { uint64_t str_size = 0; str_size = cas_prop_strnlen(value, MAX_STRING_SIZE) + 1; if ((*offset + str_size) > size) return -ENOMEM; memcpy(buffer + *offset, value, str_size); *offset += str_size; return 0; } static int _cas_prop_parse_string(const char *buffer, const uint64_t size, uint64_t *offset, char **str) { char *tmp_str = NULL; uint64_t str_size = 0; if (*offset >= size) return -ENOMEM; str_size = cas_prop_strnlen(&buffer[*offset], size - *offset ) + 1; if (str_size > size - *offset) { /* no null terminator at the end of buffer */ return -ENOMEM; } tmp_str = kstrdup(&buffer[*offset], GFP_KERNEL); if (!tmp_str) return -ENOMEM; *offset += str_size; *str = tmp_str; return 0; } static int _cas_prop_serialize_int(char *buffer, const uint64_t size, uint64_t *offset, uint64_t number) { int32_t i; /* * To prevent issue connected with byte order we * serialize integer byte by byte. */ for (i = 0; i < sizeof(number); i++) { char byte = number & 0xFF; if (*offset < size) buffer[*offset] = byte; else return -ENOMEM; (*offset)++; number = number >> 8; } return 0; } static int _cas_prop_serialize_uint(char *buffer, const uint64_t size, uint64_t *offset, uint64_t number) { return _cas_prop_serialize_int(buffer, size, offset, number); } static int _cas_prop_serialize_sint(char *buffer, const uint64_t size, uint64_t *offset, int64_t number) { return _cas_prop_serialize_int(buffer, size, offset, (uint64_t) number); } static int _cas_prop_parse_int(const char *buffer, const uint64_t size, uint64_t *offset, uint64_t *number) { int32_t i; uint64_t byte; *number = 0; /* * To prevent issue connected with byte order we * parse integer byte by byte. */ for (i = 0; i < sizeof(*number); i++) { if (*offset >= size) return -ENOMEM; byte = buffer[*offset] & 0xFF; byte = byte << (i * 8); *number |= byte; (*offset)++; } return 0; } static int _cas_prop_parse_uint(const char *buffer, const uint64_t size, uint64_t *offset, uint64_t *number) { return _cas_prop_parse_int(buffer, size, offset, number); } static int _cas_prop_parse_sint(const char *buffer, const uint64_t size, uint64_t *offset, int64_t *number) { return _cas_prop_parse_int(buffer, size, offset, (uint64_t *) number); } static int _cas_prop_serialize(struct _cas_property *entry, void *buffer, const uint64_t size, uint64_t *offset) { uint64_t item_size = 0; void *item; int result = 0; if (*offset > size) return -ENOMEM; /* * Each entry is represented in buffer in order as below * (e.g. in case we have entry with integer) : * <----- entry -----> * <- key -><-type-><- integer -> * <- X bytes -><1 byte><- 8 byte -> * | | | | */ /* * First step - serialize key */ item_size = cas_prop_strnlen(entry->key, MAX_STRING_SIZE) + 1; item = entry->key; if ((*offset + item_size) > size) return -ENOMEM; memcpy(buffer + *offset, item, item_size); *offset += item_size; /* * Second step - serialize type */ item_size = sizeof(entry->type); item = &entry->type; if ((*offset + item_size) > size) return -ENOMEM; memcpy(buffer + *offset, item, item_size); *offset += item_size; /* * Third step - serialize value */ switch (CAS_PROP_UNCONST(entry->type)) { case cas_property_string: /* Serialize string */ result = _cas_prop_serialize_string(buffer, size, offset, entry->value); break; case cas_property_sint: /* Serialize signed integer */ result = _cas_prop_serialize_sint(buffer, size, offset, entry->value_uint); break; case cas_property_uint: /* Serialize unsigned integer */ result = _cas_prop_serialize_uint(buffer, size, offset, entry->value_uint); break; default: result = -EINVAL; break; } return result; } int cas_properties_serialize(struct cas_properties *props, struct casdsk_props_conf *caches_serialized_conf) { int result = 0; uint64_t offset = 0, size; uint16_t crc = 0; void *buffer; struct list_head *curr; struct _cas_property *entry; size = _cas_prop_get_size(props); if (size == 0) return -EINVAL; buffer = vzalloc(size); if (!buffer) return -ENOMEM; /* * Update first entry on list - size of buffer */ result = cas_properties_add_uint(props, ".size", size, CAS_PROPERTIES_CONST); if (result) goto error_after_buffer_allocation; /* * Serialize each entry, one by one */ list_for_each(curr, &props->list) { entry = list_entry(curr, struct _cas_property, item); result = _cas_prop_serialize(entry, buffer, size, &offset); if (result) goto error_after_buffer_allocation; } crc = crc16(0, buffer, size); caches_serialized_conf->buffer = buffer; caches_serialized_conf->size = size; caches_serialized_conf->crc = crc; return result; error_after_buffer_allocation: vfree(buffer); return result; } void cas_properties_print(struct cas_properties *props) { struct list_head *curr; struct _cas_property *entry; char *abc; /* * Serialize each entry, one by one */ list_for_each(curr, &props->list) { entry = list_entry(curr, struct _cas_property, item); printk(KERN_DEBUG "[Upgrade] Key: %s", entry->key); switch (CAS_PROP_UNCONST(entry->type)) { case cas_property_string: printk(", string, "); abc = (char *)entry->value; printk("Value: %s ", abc); break; case cas_property_sint: break; case cas_property_uint: printk(", uint, "); printk("Value: %llu ", entry->value_uint); break; default: printk("Invalid type!"); break; } printk("\n"); } } static int _cas_prop_parse_version(const char *buffer, uint64_t *offset, uint64_t *version, int trigger) { int result = 0; char *key = NULL; uint8_t type; result = _cas_prop_parse_string(buffer, strlen(VERSION_STR) + 1, offset, &key); if (result) goto error_during_parse_key; if (strcmp(VERSION_STR, key)) { result = -EINVAL; goto error_after_parse_key; } type = buffer[*offset]; if (cas_property_uint_const != type) { result = -EINVAL; goto error_after_parse_key; } *offset += sizeof(type); result = _cas_prop_parse_uint(buffer, strlen(VERSION_STR) + 1 + sizeof(type) + sizeof(*version), offset, version); if (result) goto error_after_parse_key; /* * In case that is external call * we don't need check version. */ if (trigger == INTERNAL_CALL && *version != CAS_PROPERTIES_VERSION) { printk(KERN_ERR "Version of interface using to parse is " "different than version used to serialize\n"); result = -EPERM; } error_after_parse_key: kfree(key); error_during_parse_key: return result; } int cas_properites_parse_version(struct casdsk_props_conf *caches_serialized_conf, uint64_t *version) { uint64_t offset = 0; char *buffer = NULL; buffer = (char *) caches_serialized_conf->buffer; if (!buffer) return -EINVAL; return _cas_prop_parse_version(buffer, &offset, version, EXTERNAL_CALL); } struct cas_properties * cas_properites_parse(struct casdsk_props_conf *caches_serialized_conf) { struct cas_properties *props; char *key = NULL, *value = NULL, *buffer = NULL; int result; uint8_t type; uint64_t uint_value, size = 0, offset = 0, version = 0; uint16_t crc; int64_t sint_value; bool constant = false; props = cas_properties_create(); if (IS_ERR(props)) return ERR_PTR(-ENOMEM); if (!caches_serialized_conf) { result = -EINVAL; goto error_after_props_allocation; } buffer = (char *) caches_serialized_conf->buffer; if (!buffer) { result = -EINVAL; goto error_after_props_allocation; } size = caches_serialized_conf->size; crc = crc16(0, buffer, size); if (crc != caches_serialized_conf->crc) { printk(KERN_ERR "Cache configuration corrupted"); result = -EINVAL; goto error_after_props_allocation; } /* * Parse first entry on list - version of interface used to * serialization */ result = _cas_prop_parse_version(buffer, &offset, &version, INTERNAL_CALL); if (result) goto error_after_props_allocation; while (offset < size) { /* * Parse key of entry */ result = _cas_prop_parse_string(buffer, size, &offset, &key); if (result) goto error_after_props_allocation; /* * Parse type of entry */ if (offset + sizeof(type) > size) { kfree(key); goto error_after_props_allocation; } memcpy(&type, buffer + offset, sizeof(type)); offset += sizeof(type); constant = CAS_PROP_CHECK_CONST(type); type = CAS_PROP_UNCONST(type); switch (type) { case cas_property_string: /* Parse string */ result = _cas_prop_parse_string(buffer, size, &offset, &value); if (result) break; /* * Add new entry with string to CAS properties instance */ result |= cas_properties_add_string(props, key, value, constant); kfree(value); break; case cas_property_sint: /* Parse signed integer */ result = _cas_prop_parse_sint(buffer, size, &offset, &sint_value); /* Add new entry with signed integer to CAS properties * instance */ result |= cas_properties_add_sint(props, key, sint_value, constant); break; case cas_property_uint: /* Parse unsigned integer */ result = _cas_prop_parse_uint(buffer, size, &offset, &uint_value); /* Add new entry with unsigned integer to CAS properties * instance */ result |= cas_properties_add_uint(props, key, uint_value, constant); break; default: result = -EINVAL; break; } /* * In case when we added new entry, * we not need hold key value longer. */ kfree(key); if (result) goto error_after_props_allocation; } return props; error_after_props_allocation: cas_properties_destroy(props); return ERR_PTR(result); } static struct _cas_property *_cas_prop_find(const struct cas_properties *props, const char *key) { struct list_head *curr; struct _cas_property *entry; list_for_each(curr, &props->list) { entry = list_entry(curr, struct _cas_property, item); if (strncmp(key, entry->key, MAX_STRING_SIZE) == 0) return entry; } return ERR_PTR(-ENOENT); } static struct _cas_property *_cas_prop_alloc_entry_key(const char *key) { struct _cas_property *entry; entry = kzalloc(sizeof(*entry), GFP_KERNEL); if (!entry) return ERR_PTR(-ENOMEM); entry->key = kstrdup(key, GFP_KERNEL); if (!entry->key) { kfree(entry); return ERR_PTR(-ENOMEM); } INIT_LIST_HEAD(&entry->item); return entry; } /* * ADD */ int cas_properties_add_uint(struct cas_properties *props, const char *key, uint64_t value, bool constant) { struct _cas_property *entry; /* * Looks for entry with same key, * if it is exist - update, if not - create new */ entry = _cas_prop_find(props, key); if (IS_ERR(entry)) { entry = _cas_prop_alloc_entry_key(key); if (IS_ERR(entry)) return PTR_ERR(entry); list_add_tail(&entry->item, &props->list); } else if (cas_property_uint != entry->type) { /* * We can update only non constant entry, * so we need compare type only with non constant type. */ return -EINVAL; } entry->type = constant ? cas_property_uint_const : cas_property_uint; entry->value_uint = value; return 0; } int cas_properties_add_sint(struct cas_properties *props, const char *key, int64_t value, bool constant) { struct _cas_property *entry; /* * Looks for entry with same key, * if it is exist - update, if not - create new */ entry = _cas_prop_find(props, key); if (IS_ERR(entry)) { entry = _cas_prop_alloc_entry_key(key); if (IS_ERR(entry)) return PTR_ERR(entry); list_add_tail(&entry->item, &props->list); } else if (cas_property_sint != entry->type) { /* * We can update only non constant entry, * so we need compare type only with non constant type. */ return -EINVAL; } entry->type = constant ? cas_property_sint_const : cas_property_sint; entry->value_sint = value; return 0; } int cas_properties_add_string(struct cas_properties *props, const char *key, const char *value, bool constant) { struct _cas_property *entry; char *tmp_value = NULL; tmp_value = kstrdup(value, GFP_KERNEL); if (!tmp_value) return -ENOMEM; /* * Looks for entry with same key, * if it is exist - update, if not - create new */ entry = _cas_prop_find(props, key); if (IS_ERR(entry)) { entry = _cas_prop_alloc_entry_key(key); if (IS_ERR(entry)) { kfree(tmp_value); return PTR_ERR(entry); } list_add_tail(&entry->item, &props->list); } else { if (cas_property_string != entry->type) { /* * We can update only non constant entry, * so we need compare type only with non constant type. */ kfree(tmp_value); return -EINVAL; } kfree(entry->value); } entry->type = constant ? cas_property_string_const : cas_property_string; entry->value = tmp_value; return 0; } /* * GET */ int cas_properties_get_uint(struct cas_properties *props, const char *key, uint64_t *value) { struct _cas_property *entry; entry = _cas_prop_find(props, key); if ((IS_ERR(entry) == 0) && (cas_property_uint == CAS_PROP_UNCONST(entry->type))) { *value = entry->value_uint; return 0; } return IS_ERR(entry) ? PTR_ERR(entry) : -EINVAL; } int cas_properties_get_sint(struct cas_properties *props, const char *key, int64_t *value) { struct _cas_property *entry; entry = _cas_prop_find(props, key); if ((IS_ERR(entry) == 0) && (cas_property_sint == CAS_PROP_UNCONST(entry->type))) { *value = entry->value_sint; return 0; } return IS_ERR(entry) ? PTR_ERR(entry) : -EINVAL; } int cas_properties_get_string(struct cas_properties *props, const char *key, char *value, uint32_t size) { struct _cas_property *entry; entry = _cas_prop_find(props, key); if ((IS_ERR(entry) == 0) && (cas_property_string == CAS_PROP_UNCONST(entry->type))) { /* Check if size of destination memory is enough */ if (size < cas_prop_strnlen(entry->value, MAX_STRING_SIZE) + 1) return -ENOMEM; cas_prop_strncpy(value, size, entry->value, cas_prop_strnlen(entry->value, MAX_STRING_SIZE)); return 0; } return IS_ERR(entry) ? PTR_ERR(entry) : -EINVAL; }