| /* |
| * |
| * (C) COPYRIGHT 2017 ARM Limited. All rights reserved. |
| * |
| * This program is free software and is provided to you under the terms of the |
| * GNU General Public License version 2 as published by the Free Software |
| * Foundation, and any use by you of this program is subject to the terms |
| * of such GNU licence. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| * SPDX-License-Identifier: GPL-2.0 |
| * |
| */ |
| |
| /* Kernel UTF test helpers that mirror those for kutf-userside */ |
| #include <kutf/kutf_helpers_user.h> |
| #include <kutf/kutf_helpers.h> |
| #include <kutf/kutf_utils.h> |
| |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/export.h> |
| |
| const char *valtype_names[] = { |
| "INVALID", |
| "U64", |
| "STR", |
| }; |
| |
| static const char *get_val_type_name(enum kutf_helper_valtype valtype) |
| { |
| /* enums can be signed or unsigned (implementation dependant), so |
| * enforce it to prevent: |
| * a) "<0 comparison on unsigned type" warning - if we did both upper |
| * and lower bound check |
| * b) incorrect range checking if it was a signed type - if we did |
| * upper bound check only */ |
| unsigned int type_idx = (unsigned int)valtype; |
| |
| if (type_idx >= (unsigned int)KUTF_HELPER_VALTYPE_COUNT) |
| type_idx = (unsigned int)KUTF_HELPER_VALTYPE_INVALID; |
| |
| return valtype_names[type_idx]; |
| } |
| |
| /* Check up to str_len chars of val_str to see if it's a valid value name: |
| * |
| * - Has between 1 and KUTF_HELPER_MAX_VAL_NAME_LEN characters before the \0 terminator |
| * - And, each char is in the character set [A-Z0-9_] */ |
| static int validate_val_name(const char *val_str, int str_len) |
| { |
| int i = 0; |
| |
| for (i = 0; str_len && i <= KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0'; ++i, --str_len) { |
| char val_chr = val_str[i]; |
| |
| if (val_chr >= 'A' && val_chr <= 'Z') |
| continue; |
| if (val_chr >= '0' && val_chr <= '9') |
| continue; |
| if (val_chr == '_') |
| continue; |
| |
| /* Character not in the set [A-Z0-9_] - report error */ |
| return 1; |
| } |
| |
| /* Names of 0 length are not valid */ |
| if (i == 0) |
| return 1; |
| /* Length greater than KUTF_HELPER_MAX_VAL_NAME_LEN not allowed */ |
| if (i > KUTF_HELPER_MAX_VAL_NAME_LEN || (i == KUTF_HELPER_MAX_VAL_NAME_LEN && val_str[i] != '\0')) |
| return 1; |
| |
| return 0; |
| } |
| |
| /* Find the length of the valid part of the string when it will be in quotes |
| * e.g. "str" |
| * |
| * That is, before any '\\', '\n' or '"' characters. This is so we don't have |
| * to escape the string */ |
| static int find_quoted_string_valid_len(const char *str) |
| { |
| char *ptr; |
| const char *check_chars = "\\\n\""; |
| |
| ptr = strpbrk(str, check_chars); |
| if (ptr) |
| return (int)(ptr-str); |
| |
| return (int)strlen(str); |
| } |
| |
| static int kutf_helper_userdata_enqueue(struct kutf_context *context, |
| const char *str) |
| { |
| char *str_copy; |
| size_t len; |
| int err; |
| |
| len = strlen(str)+1; |
| |
| str_copy = kutf_mempool_alloc(&context->fixture_pool, len); |
| if (!str_copy) |
| return -ENOMEM; |
| |
| strcpy(str_copy, str); |
| |
| err = kutf_add_result(context, KUTF_RESULT_USERDATA, str_copy); |
| |
| return err; |
| } |
| |
| #define MAX_U64_HEX_LEN 16 |
| /* (Name size) + ("=0x" size) + (64-bit hex value size) + (terminator) */ |
| #define NAMED_U64_VAL_BUF_SZ (KUTF_HELPER_MAX_VAL_NAME_LEN + 3 + MAX_U64_HEX_LEN + 1) |
| |
| int kutf_helper_send_named_u64(struct kutf_context *context, |
| const char *val_name, u64 val) |
| { |
| int ret = 1; |
| char msgbuf[NAMED_U64_VAL_BUF_SZ]; |
| const char *errmsg = NULL; |
| |
| if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send u64 value named '%s': Invalid value name", val_name); |
| goto out_err; |
| } |
| |
| ret = snprintf(msgbuf, NAMED_U64_VAL_BUF_SZ, "%s=0x%llx", val_name, val); |
| if (ret >= NAMED_U64_VAL_BUF_SZ || ret < 0) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send u64 value named '%s': snprintf() problem buffer size==%d ret=%d", |
| val_name, NAMED_U64_VAL_BUF_SZ, ret); |
| goto out_err; |
| } |
| |
| ret = kutf_helper_userdata_enqueue(context, msgbuf); |
| if (ret) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send u64 value named '%s': send returned %d", |
| val_name, ret); |
| goto out_err; |
| } |
| |
| return ret; |
| out_err: |
| kutf_test_fail(context, errmsg); |
| return ret; |
| } |
| EXPORT_SYMBOL(kutf_helper_send_named_u64); |
| |
| #define NAMED_VALUE_SEP "=" |
| #define NAMED_STR_START_DELIM NAMED_VALUE_SEP "\"" |
| #define NAMED_STR_END_DELIM "\"" |
| |
| int kutf_helper_max_str_len_for_kern(const char *val_name, |
| int kern_buf_sz) |
| { |
| const int val_name_len = strlen(val_name); |
| const int start_delim_len = strlen(NAMED_STR_START_DELIM); |
| const int end_delim_len = strlen(NAMED_STR_END_DELIM); |
| int max_msg_len = kern_buf_sz; |
| int max_str_len; |
| |
| max_str_len = max_msg_len - val_name_len - start_delim_len - |
| end_delim_len; |
| |
| return max_str_len; |
| } |
| EXPORT_SYMBOL(kutf_helper_max_str_len_for_kern); |
| |
| int kutf_helper_send_named_str(struct kutf_context *context, |
| const char *val_name, |
| const char *val_str) |
| { |
| int val_str_len; |
| int str_buf_sz; |
| char *str_buf = NULL; |
| int ret = 1; |
| char *copy_ptr; |
| int val_name_len; |
| int start_delim_len = strlen(NAMED_STR_START_DELIM); |
| int end_delim_len = strlen(NAMED_STR_END_DELIM); |
| const char *errmsg = NULL; |
| |
| if (validate_val_name(val_name, KUTF_HELPER_MAX_VAL_NAME_LEN + 1)) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send u64 value named '%s': Invalid value name", val_name); |
| goto out_err; |
| } |
| val_name_len = strlen(val_name); |
| |
| val_str_len = find_quoted_string_valid_len(val_str); |
| |
| /* (name length) + ("=\"" length) + (val_str len) + ("\"" length) + terminator */ |
| str_buf_sz = val_name_len + start_delim_len + val_str_len + end_delim_len + 1; |
| |
| /* Using kmalloc() here instead of mempool since we know we need to free |
| * before we return */ |
| str_buf = kmalloc(str_buf_sz, GFP_KERNEL); |
| if (!str_buf) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send str value named '%s': kmalloc failed, str_buf_sz=%d", |
| val_name, str_buf_sz); |
| goto out_err; |
| } |
| copy_ptr = str_buf; |
| |
| /* Manually copy each string component instead of snprintf because |
| * val_str may need to end early, and less error path handling */ |
| |
| /* name */ |
| memcpy(copy_ptr, val_name, val_name_len); |
| copy_ptr += val_name_len; |
| |
| /* str start delimiter */ |
| memcpy(copy_ptr, NAMED_STR_START_DELIM, start_delim_len); |
| copy_ptr += start_delim_len; |
| |
| /* str value */ |
| memcpy(copy_ptr, val_str, val_str_len); |
| copy_ptr += val_str_len; |
| |
| /* str end delimiter */ |
| memcpy(copy_ptr, NAMED_STR_END_DELIM, end_delim_len); |
| copy_ptr += end_delim_len; |
| |
| /* Terminator */ |
| *copy_ptr = '\0'; |
| |
| ret = kutf_helper_userdata_enqueue(context, str_buf); |
| |
| if (ret) { |
| errmsg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to send str value named '%s': send returned %d", |
| val_name, ret); |
| goto out_err; |
| } |
| |
| kfree(str_buf); |
| return ret; |
| |
| out_err: |
| kutf_test_fail(context, errmsg); |
| kfree(str_buf); |
| return ret; |
| } |
| EXPORT_SYMBOL(kutf_helper_send_named_str); |
| |
| int kutf_helper_receive_named_val( |
| struct kutf_context *context, |
| struct kutf_helper_named_val *named_val) |
| { |
| size_t recv_sz; |
| char *recv_str; |
| char *search_ptr; |
| char *name_str = NULL; |
| int name_len; |
| int strval_len; |
| enum kutf_helper_valtype type = KUTF_HELPER_VALTYPE_INVALID; |
| char *strval = NULL; |
| u64 u64val = 0; |
| int err = KUTF_HELPER_ERR_INVALID_VALUE; |
| |
| recv_str = kutf_helper_input_dequeue(context, &recv_sz); |
| if (!recv_str) |
| return -EBUSY; |
| else if (IS_ERR(recv_str)) |
| return PTR_ERR(recv_str); |
| |
| /* Find the '=', grab the name and validate it */ |
| search_ptr = strnchr(recv_str, recv_sz, NAMED_VALUE_SEP[0]); |
| if (search_ptr) { |
| name_len = search_ptr - recv_str; |
| if (!validate_val_name(recv_str, name_len)) { |
| /* no need to reallocate - just modify string in place */ |
| name_str = recv_str; |
| name_str[name_len] = '\0'; |
| |
| /* Move until after the '=' */ |
| recv_str += (name_len + 1); |
| recv_sz -= (name_len + 1); |
| } |
| } |
| if (!name_str) { |
| pr_err("Invalid name part for received string '%s'\n", |
| recv_str); |
| return KUTF_HELPER_ERR_INVALID_NAME; |
| } |
| |
| /* detect value type */ |
| if (*recv_str == NAMED_STR_START_DELIM[1]) { |
| /* string delimiter start*/ |
| ++recv_str; |
| --recv_sz; |
| |
| /* Find end of string */ |
| search_ptr = strnchr(recv_str, recv_sz, NAMED_STR_END_DELIM[0]); |
| if (search_ptr) { |
| strval_len = search_ptr - recv_str; |
| /* Validate the string to ensure it contains no quotes */ |
| if (strval_len == find_quoted_string_valid_len(recv_str)) { |
| /* no need to reallocate - just modify string in place */ |
| strval = recv_str; |
| strval[strval_len] = '\0'; |
| |
| /* Move until after the end delimiter */ |
| recv_str += (strval_len + 1); |
| recv_sz -= (strval_len + 1); |
| type = KUTF_HELPER_VALTYPE_STR; |
| } else { |
| pr_err("String value contains invalid characters in rest of received string '%s'\n", recv_str); |
| err = KUTF_HELPER_ERR_CHARS_AFTER_VAL; |
| } |
| } else { |
| pr_err("End of string delimiter not found in rest of received string '%s'\n", recv_str); |
| err = KUTF_HELPER_ERR_NO_END_DELIMITER; |
| } |
| } else { |
| /* possibly a number value - strtoull will parse it */ |
| err = kstrtoull(recv_str, 0, &u64val); |
| /* unlike userspace can't get an end ptr, but if kstrtoull() |
| * reads characters after the number it'll report -EINVAL */ |
| if (!err) { |
| int len_remain = strnlen(recv_str, recv_sz); |
| |
| type = KUTF_HELPER_VALTYPE_U64; |
| recv_str += len_remain; |
| recv_sz -= len_remain; |
| } else { |
| /* special case: not a number, report as such */ |
| pr_err("Rest of received string was not a numeric value or quoted string value: '%s'\n", recv_str); |
| } |
| } |
| |
| if (type == KUTF_HELPER_VALTYPE_INVALID) |
| return err; |
| |
| /* Any remaining characters - error */ |
| if (strnlen(recv_str, recv_sz) != 0) { |
| pr_err("Characters remain after value of type %s: '%s'\n", |
| get_val_type_name(type), recv_str); |
| return KUTF_HELPER_ERR_CHARS_AFTER_VAL; |
| } |
| |
| /* Success - write into the output structure */ |
| switch (type) { |
| case KUTF_HELPER_VALTYPE_U64: |
| named_val->u.val_u64 = u64val; |
| break; |
| case KUTF_HELPER_VALTYPE_STR: |
| named_val->u.val_str = strval; |
| break; |
| default: |
| pr_err("Unreachable, fix kutf_helper_receive_named_val\n"); |
| /* Coding error, report as though 'run' file failed */ |
| return -EINVAL; |
| } |
| |
| named_val->val_name = name_str; |
| named_val->type = type; |
| |
| return KUTF_HELPER_ERR_NONE; |
| } |
| EXPORT_SYMBOL(kutf_helper_receive_named_val); |
| |
| #define DUMMY_MSG "<placeholder due to test fail>" |
| int kutf_helper_receive_check_val( |
| struct kutf_helper_named_val *named_val, |
| struct kutf_context *context, |
| const char *expect_val_name, |
| enum kutf_helper_valtype expect_val_type) |
| { |
| int err; |
| |
| err = kutf_helper_receive_named_val(context, named_val); |
| if (err < 0) { |
| const char *msg = kutf_dsprintf(&context->fixture_pool, |
| "Failed to receive value named '%s'", |
| expect_val_name); |
| kutf_test_fail(context, msg); |
| return err; |
| } else if (err > 0) { |
| const char *msg = kutf_dsprintf(&context->fixture_pool, |
| "Named-value parse error when expecting value named '%s'", |
| expect_val_name); |
| kutf_test_fail(context, msg); |
| goto out_fail_and_fixup; |
| } |
| |
| if (strcmp(named_val->val_name, expect_val_name) != 0) { |
| const char *msg = kutf_dsprintf(&context->fixture_pool, |
| "Expecting to receive value named '%s' but got '%s'", |
| expect_val_name, named_val->val_name); |
| kutf_test_fail(context, msg); |
| goto out_fail_and_fixup; |
| } |
| |
| |
| if (named_val->type != expect_val_type) { |
| const char *msg = kutf_dsprintf(&context->fixture_pool, |
| "Expecting value named '%s' to be of type %s but got %s", |
| expect_val_name, get_val_type_name(expect_val_type), |
| get_val_type_name(named_val->type)); |
| kutf_test_fail(context, msg); |
| goto out_fail_and_fixup; |
| } |
| |
| return err; |
| |
| out_fail_and_fixup: |
| /* Produce a valid but incorrect value */ |
| switch (expect_val_type) { |
| case KUTF_HELPER_VALTYPE_U64: |
| named_val->u.val_u64 = 0ull; |
| break; |
| case KUTF_HELPER_VALTYPE_STR: |
| { |
| char *str = kutf_mempool_alloc(&context->fixture_pool, sizeof(DUMMY_MSG)); |
| |
| if (!str) |
| return -1; |
| |
| strcpy(str, DUMMY_MSG); |
| named_val->u.val_str = str; |
| break; |
| } |
| default: |
| break; |
| } |
| |
| /* Indicate that this is invalid */ |
| named_val->type = KUTF_HELPER_VALTYPE_INVALID; |
| |
| /* But at least allow the caller to continue in the test with failures */ |
| return 0; |
| } |
| EXPORT_SYMBOL(kutf_helper_receive_check_val); |
| |
| void kutf_helper_output_named_val(struct kutf_helper_named_val *named_val) |
| { |
| switch (named_val->type) { |
| case KUTF_HELPER_VALTYPE_U64: |
| pr_warn("%s=0x%llx\n", named_val->val_name, named_val->u.val_u64); |
| break; |
| case KUTF_HELPER_VALTYPE_STR: |
| pr_warn("%s=\"%s\"\n", named_val->val_name, named_val->u.val_str); |
| break; |
| case KUTF_HELPER_VALTYPE_INVALID: |
| pr_warn("%s is invalid\n", named_val->val_name); |
| break; |
| default: |
| pr_warn("%s has unknown type %d\n", named_val->val_name, named_val->type); |
| break; |
| } |
| } |
| EXPORT_SYMBOL(kutf_helper_output_named_val); |