xref: /freebsd/sys/kern/subr_exterr.c (revision 295f10230903d54c700518467c4ea4492f5c4faa)
1 /*-
2  * Copyright (c) 2026 Capabilities Limited
3  *
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * This software was developed by Capabilities Limited with funding from
7  * Innovate UK and the Department for Science, Innovation and Technology
8  * for the adoption and diffusion of CHERI technology under project
9  * 10168042 (“CheriBSD feature extraction, maturity, and testing”).
10  *
11  */
12 
13 #define	EXTERR_CATEGORY_DYNAMIC	"kern/subr_exterr.c"
14 
15 #include <sys/param.h>
16 #include <sys/exterrvar.h>
17 #include <sys/exterr_cat.h>
18 #include <sys/kernel.h>
19 #include <sys/libkern.h>
20 #include <sys/linker_set.h>
21 #include <sys/malloc.h>
22 #include <sys/linker.h>	/* Need MALLOC_DECLARE */
23 #include <sys/rwlock.h>
24 #include <sys/stddef.h>
25 #include <sys/sysctl.h>
26 
27 struct exterr_cat_span {
28 	unsigned int			first;
29 	unsigned int 			count;
30 	struct exterr_cat		**cat_sets;
31 	TAILQ_ENTRY(exterr_cat_span)	entries;
32 };
33 
34 TAILQ_HEAD(exterr_cat_span_head, exterr_cat_span) cat_span_head;
35 
36 SET_DECLARE(exterr_cats, struct exterr_cat);
37 
38 static struct exterr_cat_span kern_cats;
39 unsigned int ncats;
40 struct rwlock cat_lock;
41 
42 static bool
exterr_cat_register_set(struct exterr_cat_span * span,struct exterr_cat ** start,struct exterr_cat ** stop)43 exterr_cat_register_set(struct exterr_cat_span *span, struct exterr_cat **start,
44     struct exterr_cat **stop)
45 {
46 	struct exterr_cat **catp;
47 	ptrdiff_t count;
48 
49 	count = stop - start;
50 	if (count < 1)
51 		return (true);
52 
53 	rw_wlock(&cat_lock);
54 
55 	if (ncats + count < ncats) {
56 		printf("too many exterror categories\n");
57 		rw_wunlock(&cat_lock);
58 		return (false);
59 	}
60 
61 	span->first = ncats + 1;
62 	for (catp = start; catp < stop; catp++)
63 		(*catp)->cat = ++ncats;
64 	span->count = count;
65 	span->cat_sets = start;
66 	TAILQ_INSERT_TAIL(&cat_span_head, span, entries);
67 
68 	rw_wunlock(&cat_lock);
69 
70 	return (true);
71 }
72 
73 void
exterr_cat_register_module(struct exterr_cat ** start,struct exterr_cat ** stop)74 exterr_cat_register_module(struct exterr_cat **start, struct exterr_cat **stop)
75 {
76 	struct exterr_cat_span *span;
77 
78 	span = malloc(sizeof(*span), M_LINKER, M_WAITOK | M_ZERO);
79 	if (!exterr_cat_register_set(span, start, stop))
80 		free(span, M_LINKER);
81 }
82 
83 void
exterr_cat_unregister_module(struct exterr_cat ** start,struct exterr_cat ** stop)84 exterr_cat_unregister_module(struct exterr_cat **start,
85     struct exterr_cat **stop)
86 {
87 	struct exterr_cat_span *span;
88 
89 	if (stop - start < 1)
90 		return;
91 
92 	rw_wlock(&cat_lock);
93 
94 	TAILQ_FOREACH(span, &cat_span_head, entries) {
95 		if (span->cat_sets == start) {
96 			MPASS(span->first > 1);
97 			MPASS(span->count == stop - start);
98 			TAILQ_REMOVE(&cat_span_head, span, entries);
99 			break;
100 		}
101 	}
102 	KASSERT(span != NULL, ("start not found in spans"));
103 
104 	/*
105 	 * NB: we leak category numbers on module unload because we can't
106 	 * reasonably know which ones are in use in running software.
107 	 */
108 
109 	rw_wunlock(&cat_lock);
110 
111 	free(span, M_LINKER);
112 }
113 
114 static void
exterr_cat_register_kern(void * arg)115 exterr_cat_register_kern(void *arg)
116 {
117 	rw_init(&cat_lock, "exterr dynamic categories");
118 
119 	TAILQ_INIT(&cat_span_head);
120 
121 	if (SET_COUNT(exterr_cats) == 0)
122 		return;
123 
124 	exterr_cat_register_set(&kern_cats, SET_BEGIN(exterr_cats),
125 	    SET_LIMIT(exterr_cats));
126 }
127 SYSINIT(exterr, SI_SUB_KMEM, SI_ORDER_FIRST, exterr_cat_register_kern, NULL);
128 
129 static int
sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)130 sysctl_exterr_categories(SYSCTL_HANDLER_ARGS)
131 {
132 	struct exterr_cat_span *span;
133 	const struct exterr_cat *cat = NULL;
134 	int idx;
135 
136 	if (arg2 != 1)
137 		return (EXTERROR(EINVAL,
138 		    "too many args to kern.exterr.categories %d", arg2));
139 
140 	idx = *(int *)arg1;
141 
142 	rw_rlock(&cat_lock);
143 	if (idx < 1 || idx > ncats) {
144 		rw_runlock(&cat_lock);
145 		return (EXTERROR(EINVAL, "category %d out of range (1...%d)",
146 		    idx, ncats));
147 	}
148 
149 	TAILQ_FOREACH(span, &cat_span_head, entries) {
150 		if (idx < span->first)
151 			break;	/* Not here any more */
152 
153 		if (idx < span->first + span->count)
154 			cat = span->cat_sets[idx - span->first];
155 	}
156 	rw_runlock(&cat_lock);
157 
158 	if (cat == NULL)
159 		return (EXTERROR(ENOENT, "category not found %d", idx));
160 	MPASS(cat->cat == idx);
161 	return (SYSCTL_OUT(req, cat->file, strlen(cat->file) + 1));
162 }
163 
164 SYSCTL_NODE(_kern, OID_AUTO, exterr, CTLFLAG_RD | CTLFLAG_MPSAFE, 0,
165     "Extended error information");
166 SYSCTL_UINT(_kern_exterr, OID_AUTO, ncategories, CTLFLAG_RD | CTLFLAG_MPSAFE,
167     &ncats, 0, "Number of dynamic categories");
168 SYSCTL_NODE(_kern_exterr, OID_AUTO, categories, CTLFLAG_RD | CTLFLAG_MPSAFE,
169     sysctl_exterr_categories, "Extended error categories");
170