git: 295f10230903 - main - exterror(9): dynamic kernel categories

Brooks Davis <[email protected]> Mon, 03 Aug 2026 21:44:06 +0000
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a710ba6.3bc5b.16ea80c3__23676.0827641532$1785793518$gmane$org@gitrepo.freebsd.org>
The branch main has been updated by brooks:

URL: https://cgit.FreeBSD.org/src/commit/?id=295f10230903d54c700518467c4ea4492f5c4faa

commit 295f10230903d54c700518467c4ea4492f5c4faa
Author:     Brooks Davis <[email protected]>
AuthorDate: 2026-08-03 16:50:01 +0000
Commit:     Brooks Davis <[email protected]>
CommitDate: 2026-08-03 21:43:23 +0000

    exterror(9): dynamic kernel categories
    
    Make it possible to define categories without compiling their
    paths into libc (important for third-party modules).  The
    EXTERR_CATEGORY_DYNAMIC macro can be defined to a string describing the
    compilation unit (generally the path relative to src/sys) which takes
    the place of EXTERR_CATEGORY.
    
    These strings are assembled in linker sets with category numbers
    assigned at system startup or module load time.  The strings can be
    retrieved from the kern.exterr.categories.<category> sysctl.
    
    Reviewed by:    kib
    Sponsored by:   Innovate UK
    Differential Revision:  https://reviews.freebsd.org/D58237
---
 share/man/man9/exterror.9    |  11 ++-
 sys/conf/files               |   1 +
 sys/kern/kern_linker.c       |  36 +++++++++
 sys/kern/subr_exterr.c       | 169 +++++++++++++++++++++++++++++++++++++++++++
 sys/sys/exterrvar.h          |  22 ++++++
 tests/sys/kern/exterr_test.c |  27 +++++++
 6 files changed, 264 insertions(+), 2 deletions(-)

diff --git a/share/man/man9/exterror.9 b/share/man/man9/exterror.9
index 2c9dc73f7616..a7e63abab577 100644
--- a/share/man/man9/exterror.9
+++ b/share/man/man9/exterror.9
@@ -6,7 +6,7 @@
 .\" Konstantin Belousov <[email protected]> under sponsorship
 .\" from the FreeBSD Foundation.
 .\"
-.Dd July 21, 2026
+.Dd August 3, 2026
 .Dt EXTERROR 9
 .Os
 .Sh NAME
@@ -14,6 +14,7 @@
 .Nd provide extended error information to userspace
 .Sh SYNOPSIS
 .Bd -literal -offset left -compact
+#define EXTERR_CATEGORY_DYNAMIC "path/to/this/file.c"
 #define	EXTERR_CATEGORY EXTERR_CAT_MYCATEGORY
 .Ed
 .In sys/exterrvar.h
@@ -56,7 +57,13 @@ is to make it easier for a user to identify the cause of the error.
 Before
 .Nm
 can be used in the given source .c file, the category of extended errors
-should be allocated in the
+must be defined.
+This can be done by setting the
+.Va EXTERR_CATEGORY_DYNAMIC
+macro to a string containing the name of the file relative to
+.Pa src/sys
+directory.
+Alternatively, an entry may be allocated in the
 .In sys/exterr_cat.h
 file.
 The category is the unique integer, that, together with the source
diff --git a/sys/conf/files b/sys/conf/files
index 1c52b432442f..165bcba4356b 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3988,6 +3988,7 @@ kern/subr_disk.c		standard
 kern/subr_early.c		standard
 kern/subr_epoch.c		standard
 kern/subr_eventhandler.c	standard
+kern/subr_exterr.c		standard
 kern/subr_fattime.c		standard
 kern/subr_firmware.c		optional firmware
 kern/subr_filter.c		standard
diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c
index b183d18eaea6..69b47fa6dd17 100644
--- a/sys/kern/kern_linker.c
+++ b/sys/kern/kern_linker.c
@@ -331,6 +331,40 @@ linker_file_register_sysctls(linker_file_t lf, bool enable)
 	sx_xlock(&kld_sx);
 }
 
+static void
+linker_file_register_exterr(linker_file_t lf)
+{
+	struct exterr_cat **start, **stop;
+
+	KLD_DPF(FILE,
+	    (__func__ ": registering exterror categories for %s\n",
+	    lf->filename));
+
+	sx_assert(&kld_sx, SA_XLOCKED);
+
+	if (linker_file_lookup_set(lf, "exterr_cats", &start, &stop, NULL) != 0)
+		return;
+
+	exterr_cat_register_module(start, stop);
+}
+
+static void
+linker_file_unregister_exterr(linker_file_t lf)
+{
+	struct exterr_cat **start, **stop;
+
+	KLD_DPF(FILE,
+	    (__func__ ": unregistering exterror categories for %s\n",
+	    lf->filename));
+
+	sx_assert(&kld_sx, SA_XLOCKED);
+
+	if (linker_file_lookup_set(lf, "exterr_cats", &start, &stop, NULL) != 0)
+		return;
+
+	exterr_cat_unregister_module(start, stop);
+}
+
 /*
  * Invoke the LINKER_CTF_GET implementation for this file.  Existing
  * implementations will load CTF info from the filesystem upon the first call
@@ -500,6 +534,7 @@ linker_load_file(const char *filename, linker_file_t *result)
 #ifdef VIMAGE
 			LINKER_PROPAGATE_VNETS(lf);
 #endif
+			linker_file_register_exterr(lf);
 			linker_file_sysinit(lf);
 			lf->flags |= LINKER_FILE_LINKED;
 
@@ -775,6 +810,7 @@ linker_file_unload(linker_file_t file, int flags)
 	if ((file->flags & LINKER_FILE_LINKED) != 0) {
 		file->flags &= ~LINKER_FILE_LINKED;
 		linker_file_unregister_sysctls(file);
+		linker_file_unregister_exterr(file);
 		linker_file_sysuninit(file);
 		EVENTHANDLER_INVOKE(kld_unload, file->filename, file->address,
 		    file->size);
diff --git a/sys/kern/subr_exterr.c b/sys/kern/subr_exterr.c
new file mode 100644
index 000000000000..b704d3ba1dae
--- /dev/null
+++ b/sys/kern/subr_exterr.c
@@ -0,0 +1,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");
diff --git a/sys/sys/exterrvar.h b/sys/sys/exterrvar.h
index a4f6d5313efc..e0822288d746 100644
--- a/sys/sys/exterrvar.h
+++ b/sys/sys/exterrvar.h
@@ -14,6 +14,9 @@
 #include <sys/_exterr.h>
 #include <sys/_uexterror.h>
 #include <sys/exterr_cat.h>
+#ifdef EXTERR_CATEGORY_DYNAMIC
+#include <sys/linker_set.h>
+#endif
 
 #define	UEXTERROR_MAXLEN	256
 
@@ -27,8 +30,23 @@
 
 #ifdef _KERNEL
 
+struct exterr_cat {
+	unsigned int	cat;
+	const char	*file;
+};
+
 struct thread;
 
+#ifdef EXTERR_CATEGORY_DYNAMIC
+#ifdef EXTERR_STRINGS
+static struct exterr_cat __dynamic_cat = { .file = EXTERR_CATEGORY_DYNAMIC };
+DATA_WSET(exterr_cats, __dynamic_cat);
+#define	EXTERR_CATEGORY	(__dynamic_cat.cat | EXTERR_CAT_SRC_KERN_DYNAMIC)
+#else
+#define	EXTERR_CATEGORY	EXTERR_CAT_NONE
+#endif
+#endif
+
 #ifndef EXTERR_CATEGORY
 #error "Specify error category before including sys/exterrvar.h"
 #endif
@@ -78,6 +96,10 @@ int exterr_set(int eerror, int category, const char *mmsg, uint64ptr_t pp1,
     uint64ptr_t pp2, int line);
 int exterr_to_ue(struct thread *td, struct uexterror *ue);
 void ktrexterr(struct thread *td);
+void exterr_cat_register_module(struct exterr_cat **start,
+    struct exterr_cat **stop);
+void exterr_cat_unregister_module(struct exterr_cat **start,
+    struct exterr_cat **stop);
 
 #else	/* !_KERNEL */
 
diff --git a/tests/sys/kern/exterr_test.c b/tests/sys/kern/exterr_test.c
index bde7654fecfc..fad002767163 100644
--- a/tests/sys/kern/exterr_test.c
+++ b/tests/sys/kern/exterr_test.c
@@ -23,8 +23,10 @@
  * SUCH DAMAGE.
  */
 
+#include <sys/param.h>
 #include <sys/exterrvar.h>
 #include <sys/mman.h>
+#include <sys/sysctl.h>
 
 #include <atf-c.h>
 #include <errno.h>
@@ -121,11 +123,36 @@ ATF_TC_BODY(gettext_noextended_after_extended, tc)
 	ATF_CHECK_STREQ(exterr, "");
 }
 
+ATF_TC(exterr_dynamic_categories);
+ATF_TC_HEAD(exterr_dynamic_categories, tc)
+{
+	atf_tc_set_md_var(tc, "descr",
+	    "directly check there is at least one registered category");
+}
+ATF_TC_BODY(exterr_dynamic_categories, tc)
+{
+        int mib[4];
+        size_t len;
+        char filename_buf[128];
+
+        len = nitems(mib);
+        ATF_REQUIRE_EQ(sysctlnametomib("kern.exterr.categories", mib, &len),
+	     0);
+        mib[3] = 1;
+        len = sizeof(filename_buf);
+        ATF_REQUIRE_EQ(sysctl(mib, nitems(mib), filename_buf, &len, NULL, 0),
+	     0);
+        printf("%s\n", filename_buf);
+	/* We can't know what it is, but make sure it's non-empty */
+	ATF_REQUIRE(strlen(filename_buf) > 1);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
 	ATF_TP_ADD_TC(tp, gettext_extended);
 	ATF_TP_ADD_TC(tp, gettext_noextended);
 	ATF_TP_ADD_TC(tp, gettext_noextended_after_extended);
+	ATF_TP_ADD_TC(tp, exterr_dynamic_categories);
 
 	return (atf_no_error());
 }