| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * Copyright 2023 Red Hat | |
| */ | |
| #include "errors.h" | |
| #include <linux/compiler.h> | |
| #include <linux/errno.h> | |
| #include "logger.h" | |
| #include "permassert.h" | |
| #include "string-utils.h" | |
| static const struct error_info successful = { "UDS_SUCCESS", "Success" }; | |
| static const char *const message_table[] = { | |
| [EPERM] = "Operation not permitted", | |
| [ENOENT] = "No such file or directory", | |
| [ESRCH] = "No such process", | |
| [EINTR] = "Interrupted system call", | |
| [EIO] = "Input/output error", | |
| [ENXIO] = "No such device or address", | |
| [E2BIG] = "Argument list too long", | |
| [ENOEXEC] = "Exec format error", | |
| [EBADF] = "Bad file descriptor", | |
| [ECHILD] = "No child processes", | |
| [EAGAIN] = "Resource temporarily unavailable", | |
| [ENOMEM] = "Cannot allocate memory", | |
| [EACCES] = "Permission denied", | |
| [EFAULT] = "Bad address", | |
| [ENOTBLK] = "Block device required", | |
| [EBUSY] = "Device or resource busy", | |
| [EEXIST] = "File exists", | |
| [EXDEV] = "Invalid cross-device link", | |
| [ENODEV] = "No such device", | |
| [ENOTDIR] = "Not a directory", | |
| [EISDIR] = "Is a directory", | |
| [EINVAL] = "Invalid argument", | |
| [ENFILE] = "Too many open files in system", | |
| [EMFILE] = "Too many open files", | |
| [ENOTTY] = "Inappropriate ioctl for device", | |
| [ETXTBSY] = "Text file busy", | |
| [EFBIG] = "File too large", | |
| [ENOSPC] = "No space left on device", | |
| [ESPIPE] = "Illegal seek", | |
| [EROFS] = "Read-only file system", | |
| [EMLINK] = "Too many links", | |
| [EPIPE] = "Broken pipe", | |
| [EDOM] = "Numerical argument out of domain", | |
| [ERANGE] = "Numerical result out of range" | |
| }; | |
| static const struct error_info error_list[] = { | |
| { "UDS_OVERFLOW", "Index overflow" }, | |
| { "UDS_INVALID_ARGUMENT", "Invalid argument passed to internal routine" }, | |
| { "UDS_BAD_STATE", "UDS data structures are in an invalid state" }, | |
| { "UDS_DUPLICATE_NAME", "Attempt to enter the same name into a delta index twice" }, | |
| { "UDS_ASSERTION_FAILED", "Assertion failed" }, | |
| { "UDS_QUEUED", "Request queued" }, | |
| { "UDS_ALREADY_REGISTERED", "Error range already registered" }, | |
| { "UDS_OUT_OF_RANGE", "Cannot access data outside specified limits" }, | |
| { "UDS_DISABLED", "UDS library context is disabled" }, | |
| { "UDS_UNSUPPORTED_VERSION", "Unsupported version" }, | |
| { "UDS_CORRUPT_DATA", "Some index structure is corrupt" }, | |
| { "UDS_NO_INDEX", "No index found" }, | |
| { "UDS_INDEX_NOT_SAVED_CLEANLY", "Index not saved cleanly" }, | |
| }; | |
| struct error_block { | |
| const char *name; | |
| int base; | |
| int last; | |
| int max; | |
| const struct error_info *infos; | |
| }; | |
| #define MAX_ERROR_BLOCKS 6 | |
| static struct { | |
| int allocated; | |
| int count; | |
| struct error_block blocks[MAX_ERROR_BLOCKS]; | |
| } registered_errors = { | |
| .allocated = MAX_ERROR_BLOCKS, | |
| .count = 1, | |
| .blocks = { { | |
| .name = "UDS Error", | |
| .base = UDS_ERROR_CODE_BASE, | |
| .last = UDS_ERROR_CODE_LAST, | |
| .max = UDS_ERROR_CODE_BLOCK_END, | |
| .infos = error_list, | |
| } }, | |
| }; | |
| /* Get the error info for an error number. Also returns the name of the error block, if known. */ | |
| static const char *get_error_info(int errnum, const struct error_info **info_ptr) | |
| { | |
| struct error_block *block; | |
| if (errnum == UDS_SUCCESS) { | |
| *info_ptr = &successful; | |
| return NULL; | |
| } | |
| for (block = registered_errors.blocks; | |
| block < registered_errors.blocks + registered_errors.count; | |
| block++) { | |
| if ((errnum >= block->base) && (errnum < block->last)) { | |
| *info_ptr = block->infos + (errnum - block->base); | |
| return block->name; | |
| } else if ((errnum >= block->last) && (errnum < block->max)) { | |
| *info_ptr = NULL; | |
| return block->name; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* Return a string describing a system error message. */ | |
| static const char *system_string_error(int errnum, char *buf, size_t buflen) | |
| { | |
| size_t len; | |
| const char *error_string = NULL; | |
| if ((errnum > 0) && (errnum < ARRAY_SIZE(message_table))) | |
| error_string = message_table[errnum]; | |
| len = ((error_string == NULL) ? | |
| snprintf(buf, buflen, "Unknown error %d", errnum) : | |
| snprintf(buf, buflen, "%s", error_string)); | |
| if (len < buflen) | |
| return buf; | |
| buf[0] = '\0'; | |
| return "System error"; | |
| } | |
| /* Convert an error code to a descriptive string. */ | |
| const char *uds_string_error(int errnum, char *buf, size_t buflen) | |
| { | |
| char *buffer = buf; | |
| char *buf_end = buf + buflen; | |
| const struct error_info *info = NULL; | |
| const char *block_name; | |
| if (buf == NULL) | |
| return NULL; | |
| if (errnum < 0) | |
| errnum = -errnum; | |
| block_name = get_error_info(errnum, &info); | |
| if (block_name != NULL) { | |
| if (info != NULL) { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s: %s", | |
| block_name, info->message); | |
| } else { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "Unknown %s %d", | |
| block_name, errnum); | |
| } | |
| } else if (info != NULL) { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->message); | |
| } else { | |
| const char *tmp = system_string_error(errnum, buffer, buf_end - buffer); | |
| if (tmp != buffer) | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s", tmp); | |
| else | |
| buffer += strlen(tmp); | |
| } | |
| return buf; | |
| } | |
| /* Convert an error code to its name. */ | |
| const char *uds_string_error_name(int errnum, char *buf, size_t buflen) | |
| { | |
| char *buffer = buf; | |
| char *buf_end = buf + buflen; | |
| const struct error_info *info = NULL; | |
| const char *block_name; | |
| if (errnum < 0) | |
| errnum = -errnum; | |
| block_name = get_error_info(errnum, &info); | |
| if (block_name != NULL) { | |
| if (info != NULL) { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->name); | |
| } else { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s %d", | |
| block_name, errnum); | |
| } | |
| } else if (info != NULL) { | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->name); | |
| } else { | |
| const char *tmp; | |
| tmp = system_string_error(errnum, buffer, buf_end - buffer); | |
| if (tmp != buffer) | |
| buffer = vdo_append_to_buffer(buffer, buf_end, "%s", tmp); | |
| else | |
| buffer += strlen(tmp); | |
| } | |
| return buf; | |
| } | |
| /* | |
| * Translate an error code into a value acceptable to the kernel. The input error code may be a | |
| * system-generated value (such as -EIO), or an internal UDS status code. The result will be a | |
| * negative errno value. | |
| */ | |
| int uds_status_to_errno(int error) | |
| { | |
| char error_name[VDO_MAX_ERROR_NAME_SIZE]; | |
| char error_message[VDO_MAX_ERROR_MESSAGE_SIZE]; | |
| /* 0 is success, and negative values are already system error codes. */ | |
| if (likely(error <= 0)) | |
| return error; | |
| if (error < 1024) { | |
| /* This is probably an errno from userspace. */ | |
| return -error; | |
| } | |
| /* Internal UDS errors */ | |
| switch (error) { | |
| case UDS_NO_INDEX: | |
| case UDS_CORRUPT_DATA: | |
| /* The index doesn't exist or can't be recovered. */ | |
| return -ENOENT; | |
| case UDS_INDEX_NOT_SAVED_CLEANLY: | |
| case UDS_UNSUPPORTED_VERSION: | |
| /* | |
| * The index exists, but can't be loaded. Tell the client it exists so they don't | |
| * destroy it inadvertently. | |
| */ | |
| return -EEXIST; | |
| case UDS_DISABLED: | |
| /* The session is unusable; only returned by requests. */ | |
| return -EIO; | |
| default: | |
| /* Translate an unexpected error into something generic. */ | |
| vdo_log_info("%s: mapping status code %d (%s: %s) to -EIO", | |
| __func__, error, | |
| uds_string_error_name(error, error_name, | |
| sizeof(error_name)), | |
| uds_string_error(error, error_message, | |
| sizeof(error_message))); | |
| return -EIO; | |
| } | |
| } | |
| /* | |
| * Register a block of error codes. | |
| * | |
| * @block_name: the name of the block of error codes | |
| * @first_error: the first error code in the block | |
| * @next_free_error: one past the highest possible error in the block | |
| * @infos: a pointer to the error info array for the block | |
| * @info_size: the size of the error info array | |
| */ | |
| int uds_register_error_block(const char *block_name, int first_error, | |
| int next_free_error, const struct error_info *infos, | |
| size_t info_size) | |
| { | |
| int result; | |
| struct error_block *block; | |
| struct error_block new_block = { | |
| .name = block_name, | |
| .base = first_error, | |
| .last = first_error + (info_size / sizeof(struct error_info)), | |
| .max = next_free_error, | |
| .infos = infos, | |
| }; | |
| result = VDO_ASSERT(first_error < next_free_error, | |
| "well-defined error block range"); | |
| if (result != VDO_SUCCESS) | |
| return result; | |
| if (registered_errors.count == registered_errors.allocated) { | |
| /* This should never happen. */ | |
| return UDS_OVERFLOW; | |
| } | |
| for (block = registered_errors.blocks; | |
| block < registered_errors.blocks + registered_errors.count; | |
| block++) { | |
| if (strcmp(block_name, block->name) == 0) | |
| return UDS_DUPLICATE_NAME; | |
| /* Ensure error ranges do not overlap. */ | |
| if ((first_error < block->max) && (next_free_error > block->base)) | |
| return UDS_ALREADY_REGISTERED; | |
| } | |
| registered_errors.blocks[registered_errors.count++] = new_block; | |
| return UDS_SUCCESS; | |
| } |