| // 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; |
| } |