root/drivers/md/dm-vdo/errors.c
// 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;
}