1// SPDX-License-Identifier: GPL-2.0-only
2/*
3 * Copyright 2023 Red Hat
4 */
5
6#include "errors.h"
7
8#include <linux/compiler.h>
9#include <linux/errno.h>
10
11#include "logger.h"
12#include "permassert.h"
13#include "string-utils.h"
14
15static const struct error_info successful = { "UDS_SUCCESS", "Success" };
16
17static const char *const message_table[] = {
18	[EPERM] = "Operation not permitted",
19	[ENOENT] = "No such file or directory",
20	[ESRCH] = "No such process",
21	[EINTR] = "Interrupted system call",
22	[EIO] = "Input/output error",
23	[ENXIO] = "No such device or address",
24	[E2BIG] = "Argument list too long",
25	[ENOEXEC] = "Exec format error",
26	[EBADF] = "Bad file descriptor",
27	[ECHILD] = "No child processes",
28	[EAGAIN] = "Resource temporarily unavailable",
29	[ENOMEM] = "Cannot allocate memory",
30	[EACCES] = "Permission denied",
31	[EFAULT] = "Bad address",
32	[ENOTBLK] = "Block device required",
33	[EBUSY] = "Device or resource busy",
34	[EEXIST] = "File exists",
35	[EXDEV] = "Invalid cross-device link",
36	[ENODEV] = "No such device",
37	[ENOTDIR] = "Not a directory",
38	[EISDIR] = "Is a directory",
39	[EINVAL] = "Invalid argument",
40	[ENFILE] = "Too many open files in system",
41	[EMFILE] = "Too many open files",
42	[ENOTTY] = "Inappropriate ioctl for device",
43	[ETXTBSY] = "Text file busy",
44	[EFBIG] = "File too large",
45	[ENOSPC] = "No space left on device",
46	[ESPIPE] = "Illegal seek",
47	[EROFS] = "Read-only file system",
48	[EMLINK] = "Too many links",
49	[EPIPE] = "Broken pipe",
50	[EDOM] = "Numerical argument out of domain",
51	[ERANGE] = "Numerical result out of range"
52};
53
54static const struct error_info error_list[] = {
55	{ "UDS_OVERFLOW", "Index overflow" },
56	{ "UDS_INVALID_ARGUMENT", "Invalid argument passed to internal routine" },
57	{ "UDS_BAD_STATE", "UDS data structures are in an invalid state" },
58	{ "UDS_DUPLICATE_NAME", "Attempt to enter the same name into a delta index twice" },
59	{ "UDS_ASSERTION_FAILED", "Assertion failed" },
60	{ "UDS_QUEUED", "Request queued" },
61	{ "UDS_ALREADY_REGISTERED", "Error range already registered" },
62	{ "UDS_OUT_OF_RANGE", "Cannot access data outside specified limits" },
63	{ "UDS_DISABLED", "UDS library context is disabled" },
64	{ "UDS_UNSUPPORTED_VERSION", "Unsupported version" },
65	{ "UDS_CORRUPT_DATA", "Some index structure is corrupt" },
66	{ "UDS_NO_INDEX", "No index found" },
67	{ "UDS_INDEX_NOT_SAVED_CLEANLY", "Index not saved cleanly" },
68};
69
70struct error_block {
71	const char *name;
72	int base;
73	int last;
74	int max;
75	const struct error_info *infos;
76};
77
78#define MAX_ERROR_BLOCKS 6
79
80static struct {
81	int allocated;
82	int count;
83	struct error_block blocks[MAX_ERROR_BLOCKS];
84} registered_errors = {
85	.allocated = MAX_ERROR_BLOCKS,
86	.count = 1,
87	.blocks = { {
88			.name = "UDS Error",
89			.base = UDS_ERROR_CODE_BASE,
90			.last = UDS_ERROR_CODE_LAST,
91			.max = UDS_ERROR_CODE_BLOCK_END,
92			.infos = error_list,
93		  } },
94};
95
96/* Get the error info for an error number. Also returns the name of the error block, if known. */
97static const char *get_error_info(int errnum, const struct error_info **info_ptr)
98{
99	struct error_block *block;
100
101	if (errnum == UDS_SUCCESS) {
102		*info_ptr = &successful;
103		return NULL;
104	}
105
106	for (block = registered_errors.blocks;
107	     block < registered_errors.blocks + registered_errors.count;
108	     block++) {
109		if ((errnum >= block->base) && (errnum < block->last)) {
110			*info_ptr = block->infos + (errnum - block->base);
111			return block->name;
112		} else if ((errnum >= block->last) && (errnum < block->max)) {
113			*info_ptr = NULL;
114			return block->name;
115		}
116	}
117
118	return NULL;
119}
120
121/* Return a string describing a system error message. */
122static const char *system_string_error(int errnum, char *buf, size_t buflen)
123{
124	size_t len;
125	const char *error_string = NULL;
126
127	if ((errnum > 0) && (errnum < ARRAY_SIZE(message_table)))
128		error_string = message_table[errnum];
129
130	len = ((error_string == NULL) ?
131		 snprintf(buf, buflen, "Unknown error %d", errnum) :
132		 snprintf(buf, buflen, "%s", error_string));
133	if (len < buflen)
134		return buf;
135
136	buf[0] = '\0';
137	return "System error";
138}
139
140/* Convert an error code to a descriptive string. */
141const char *uds_string_error(int errnum, char *buf, size_t buflen)
142{
143	char *buffer = buf;
144	char *buf_end = buf + buflen;
145	const struct error_info *info = NULL;
146	const char *block_name;
147
148	if (buf == NULL)
149		return NULL;
150
151	if (errnum < 0)
152		errnum = -errnum;
153
154	block_name = get_error_info(errnum, &info);
155	if (block_name != NULL) {
156		if (info != NULL) {
157			buffer = vdo_append_to_buffer(buffer, buf_end, "%s: %s",
158						      block_name, info->message);
159		} else {
160			buffer = vdo_append_to_buffer(buffer, buf_end, "Unknown %s %d",
161						      block_name, errnum);
162		}
163	} else if (info != NULL) {
164		buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->message);
165	} else {
166		const char *tmp = system_string_error(errnum, buffer, buf_end - buffer);
167
168		if (tmp != buffer)
169			buffer = vdo_append_to_buffer(buffer, buf_end, "%s", tmp);
170		else
171			buffer += strlen(tmp);
172	}
173
174	return buf;
175}
176
177/* Convert an error code to its name. */
178const char *uds_string_error_name(int errnum, char *buf, size_t buflen)
179{
180	char *buffer = buf;
181	char *buf_end = buf + buflen;
182	const struct error_info *info = NULL;
183	const char *block_name;
184
185	if (errnum < 0)
186		errnum = -errnum;
187
188	block_name = get_error_info(errnum, &info);
189	if (block_name != NULL) {
190		if (info != NULL) {
191			buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->name);
192		} else {
193			buffer = vdo_append_to_buffer(buffer, buf_end, "%s %d",
194						      block_name, errnum);
195		}
196	} else if (info != NULL) {
197		buffer = vdo_append_to_buffer(buffer, buf_end, "%s", info->name);
198	} else {
199		const char *tmp;
200
201		tmp = system_string_error(errnum, buffer, buf_end - buffer);
202		if (tmp != buffer)
203			buffer = vdo_append_to_buffer(buffer, buf_end, "%s", tmp);
204		else
205			buffer += strlen(tmp);
206	}
207
208	return buf;
209}
210
211/*
212 * Translate an error code into a value acceptable to the kernel. The input error code may be a
213 * system-generated value (such as -EIO), or an internal UDS status code. The result will be a
214 * negative errno value.
215 */
216int uds_status_to_errno(int error)
217{
218	char error_name[VDO_MAX_ERROR_NAME_SIZE];
219	char error_message[VDO_MAX_ERROR_MESSAGE_SIZE];
220
221	/* 0 is success, and negative values are already system error codes. */
222	if (likely(error <= 0))
223		return error;
224
225	if (error < 1024) {
226		/* This is probably an errno from userspace. */
227		return -error;
228	}
229
230	/* Internal UDS errors */
231	switch (error) {
232	case UDS_NO_INDEX:
233	case UDS_CORRUPT_DATA:
234		/* The index doesn't exist or can't be recovered. */
235		return -ENOENT;
236
237	case UDS_INDEX_NOT_SAVED_CLEANLY:
238	case UDS_UNSUPPORTED_VERSION:
239		/*
240		 * The index exists, but can't be loaded. Tell the client it exists so they don't
241		 * destroy it inadvertently.
242		 */
243		return -EEXIST;
244
245	case UDS_DISABLED:
246		/* The session is unusable; only returned by requests. */
247		return -EIO;
248
249	default:
250		/* Translate an unexpected error into something generic. */
251		vdo_log_info("%s: mapping status code %d (%s: %s) to -EIO",
252			     __func__, error,
253			     uds_string_error_name(error, error_name,
254						   sizeof(error_name)),
255			     uds_string_error(error, error_message,
256					      sizeof(error_message)));
257		return -EIO;
258	}
259}
260
261/*
262 * Register a block of error codes.
263 *
264 * @block_name: the name of the block of error codes
265 * @first_error: the first error code in the block
266 * @next_free_error: one past the highest possible error in the block
267 * @infos: a pointer to the error info array for the block
268 * @info_size: the size of the error info array
269 */
270int uds_register_error_block(const char *block_name, int first_error,
271			     int next_free_error, const struct error_info *infos,
272			     size_t info_size)
273{
274	int result;
275	struct error_block *block;
276	struct error_block new_block = {
277		.name = block_name,
278		.base = first_error,
279		.last = first_error + (info_size / sizeof(struct error_info)),
280		.max = next_free_error,
281		.infos = infos,
282	};
283
284	result = VDO_ASSERT(first_error < next_free_error,
285			    "well-defined error block range");
286	if (result != VDO_SUCCESS)
287		return result;
288
289	if (registered_errors.count == registered_errors.allocated) {
290		/* This should never happen. */
291		return UDS_OVERFLOW;
292	}
293
294	for (block = registered_errors.blocks;
295	     block < registered_errors.blocks + registered_errors.count;
296	     block++) {
297		if (strcmp(block_name, block->name) == 0)
298			return UDS_DUPLICATE_NAME;
299
300		/* Ensure error ranges do not overlap. */
301		if ((first_error < block->max) && (next_free_error > block->base))
302			return UDS_ALREADY_REGISTERED;
303	}
304
305	registered_errors.blocks[registered_errors.count++] = new_block;
306	return UDS_SUCCESS;
307}
308