aboutsummaryrefslogtreecommitdiff
path: root/sys/kern/subr_exterr.c
blob: b704d3ba1dae8ec1f6bbba28a7f9ce4ab5c4666c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/*-
 * Copyright (c) 2026 Capabilities Limited
 *
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * This software was developed by Capabilities Limited with funding from
 * Innovate UK and the Department for Science, Innovation and Technology
 * for the adoption and diffusion of CHERI technology under project
 * 10168042 (“CheriBSD feature extraction, maturity, and testing”).
 *
 */

#define	EXTERR_CATEGORY_DYNAMIC	"kern/subr_exterr.c"

#include <sys/param.h>
#include <sys/exterrvar.h>
#include <sys/exterr_cat.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/linker_set.h>
#include <sys/malloc.h>
#include <sys/linker.h>	/* Need MALLOC_DECLARE */
#include <sys/rwlock.h>
#include <sys/stddef.h>
#include <sys/sysctl.h>

struct exterr_cat_span {
	unsigned int			first;
	unsigned int 			count;
	struct exterr_cat		**cat_sets;
	TAILQ_ENTRY(exterr_cat_span)	entries;
};

TAILQ_HEAD(exterr_cat_span_head, exterr_cat_span) cat_span_head;

SET_DECLARE(exterr_cats, struct exterr_cat);

static struct exterr_cat_span kern_cats;
unsigned int ncats;
struct rwlock cat_lock;

static bool
exterr_cat_register_set(struct exterr_cat_span *span, struct exterr_cat **start,
    struct exterr_cat **stop)
{
	struct exterr_cat **catp;
	ptrdiff_t count;

	count = stop - start;
	if (count < 1)
		return (true);

	rw_wlock(&cat_lock);

	if (ncats + count < ncats) {
		printf("too many exterror categories\n");
		rw_wunlock(&cat_lock);
		return (false);
	}

	span->first = ncats + 1;
	for (catp = start; catp < stop; catp++)
		(*catp)->cat = ++ncats;
	span->count = count;
	span->cat_sets = start;
	TAILQ_INSERT_TAIL(&cat_span_head, span, entries);

	rw_wunlock(&cat_lock);

	return (true);
}

void
exterr_cat_register_module(struct exterr_cat **start, struct exterr_cat **stop)
{
	struct exterr_cat_span *span;

	span = malloc(sizeof(*span), M_LINKER, M_WAITOK | M_ZERO);
	if (!exterr_cat_register_set(span, start, stop))
		free(span, M_LINKER);
}

void
exterr_cat_unregister_module(struct exterr_cat **start,
    struct exterr_cat **stop)
{
	struct exterr_cat_span *span;

	if (stop - start < 1)
		return;

	rw_wlock(&cat_lock);

	TAILQ_FOREACH(span, &cat_span_head, entries) {
		if (span->cat_sets == start) {
			MPASS(span->first > 1);
			MPASS(span->count == stop - start);
			TAILQ_REMOVE(&cat_span_head, span, entries);
			break;
		}
	}
	KASSERT(span != NULL, ("start not found in spans"));

	/*
	 * NB: we leak category numbers on module unload because we can't
	 * reasonably know which ones are in use in running software.
	 */

	rw_wunlock(&cat_lock);

	free(span, M_LINKER);
}

static void
exterr_cat_register_kern(void *arg)
{
	rw_init(&cat_lock, "exterr dynamic categories");

	TAILQ_INIT(&cat_span_head);

	if (SET_COUNT(exterr_cats) == 0)
		return;

	exterr_cat_register_set(&kern_cats, SET_BEGIN(exterr_cats),
	    SET_LIMIT(exterr_cats));
}
SYSINIT(exterr, SI_SUB_KMEM, SI_ORDER_FIRST, exterr_cat_register_kern, NULL);

static int
sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)
{
	struct exterr_cat_span *span;
	const struct exterr_cat *cat = NULL;
	int idx;

	if (arg2 != 1)
		return (EXTERROR(EINVAL,
		    "too many args to kern.exterr.categories %d", arg2));

	idx = *(int *)arg1;

	rw_rlock(&cat_lock);
	if (idx < 1 || idx > ncats) {
		rw_runlock(&cat_lock);
		return (EXTERROR(EINVAL, "category %d out of range (1...%d)",
		    idx, ncats));
	}

	TAILQ_FOREACH(span, &cat_span_head, entries) {
		if (idx < span->first)
			break;	/* Not here any more */

		if (idx < span->first + span->count)
			cat = span->cat_sets[idx - span->first];
	}
	rw_runlock(&cat_lock);

	if (cat == NULL)
		return (EXTERROR(ENOENT, "category not found %d", idx));
	MPASS(cat->cat == idx);
	return (SYSCTL_OUT(req, cat->file, strlen(cat->file) + 1));
}

SYSCTL_NODE(_kern, OID_AUTO, exterr, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
    "Extended error information");
SYSCTL_UINT(_kern_exterr, OID_AUTO, ncategories, CTLFLAG_RD | CTLFLAG_MPSAFE,
    &ncats, 0, "Number of dynamic categories");
SYSCTL_NODE(_kern_exterr, OID_AUTO, categories, CTLFLAG_RD | CTLFLAG_MPSAFE,
    sysctl_exterr_categories, "Extended error categories");