diff --git a/lib/libc/gen/err.3 b/lib/libc/gen/err.3 --- a/lib/libc/gen/err.3 +++ b/lib/libc/gen/err.3 @@ -114,6 +114,22 @@ argument is .Dv NULL . .Pp +If the kernel returned and extended error string in addition to the +.Va errno +code, the +.Fn err +function prints the string with interpolated values for parameters, +as provided to the corresponding invocation of +.Xr EXTERROR 9 . +If the extended error string was not provided, but extended error +information was, or even if string was provided and the +.Ev EXTERROR_VERBOSE +environment variable is present, an additional report is printed. +The report includes at least the category of the error, the name of +the source file (if known by the used version of libc), +the source line number, and parameters. +The format of the printed string is not contractual and might be changed. +.Pp In the case of the .Fn errc , .Fn verrc , diff --git a/lib/libc/gen/err.c b/lib/libc/gen/err.c --- a/lib/libc/gen/err.c +++ b/lib/libc/gen/err.c @@ -120,7 +120,7 @@ } fprintf(err_file, "%s", strerror(code)); if (doexterr && extstatus == 0 && exterr[0] != '\0') - fprintf(err_file, " (extended error %s)", exterr); + fprintf(err_file, " (%s)", exterr); fprintf(err_file, "\n"); } diff --git a/lib/libc/gen/exterr_cat_filenames.h b/lib/libc/gen/exterr_cat_filenames.h new file mode 100644 --- /dev/null +++ b/lib/libc/gen/exterr_cat_filenames.h @@ -0,0 +1,17 @@ +/* + * Automatically @generated, use + * tools/build/make_libc_exterr_cat_filenames.sh + */ + [EXTERR_CAT_FUSE_DEVICE] = "fs/fuse/fuse_device.c", + [EXTERR_CAT_FUSE_VFS] = "fs/fuse/fuse_vfsops.c", + [EXTERR_CAT_FUSE_VNOPS] = "fs/fuse/fuse_vnops.c", + [EXTERR_CAT_GEOM] = "geom/geom_subr.c", + [EXTERR_CAT_GEOMVFS] = "geom/geom_vfs.c", + [EXTERR_CAT_FILEDESC] = "kern/kern_descrip.c", + [EXTERR_CAT_INOTIFY] = "kern/vfs_inotify.c", + [EXTERR_CAT_GENIO] = "kern/sys_generic.c", + [EXTERR_CAT_VFSBIO] = "kern/vfs_bio.c", + [EXTERR_CAT_VFSSYSCALL] = "kern/vfs_syscalls.c", + [EXTERR_CAT_BRIDGE] = "net/if_bridge.c", + [EXTERR_CAT_SWAP] = "vm/swap_pager.c", + [EXTERR_CAT_MMAP] = "vm/vm_mmap.c", diff --git a/lib/libc/gen/uexterr_format.c b/lib/libc/gen/uexterr_format.c --- a/lib/libc/gen/uexterr_format.c +++ b/lib/libc/gen/uexterr_format.c @@ -8,28 +8,85 @@ * under sponsorship from the FreeBSD Foundation. */ -#include +#include #include #include +#include #include +#include #include +#include + +static const char * const cat_to_filenames[] = { +#include "exterr_cat_filenames.h" +}; + +static const char * +cat_to_filename(int category) +{ + if (category < 0 || category >= nitems(cat_to_filenames) || + cat_to_filenames[category] == NULL) + return ("unknown"); + return (cat_to_filenames[category]); +} + +static const char exterror_verbose_name[] = "EXTERROR_VERBOSE"; +enum exterr_verbose_state { + EXTERR_VERBOSE_UNKNOWN = 100, + EXTERR_VERBOSE_DEFAULT, + EXTERR_VERBOSE_ALLOW, +}; +static enum exterr_verbose_state exterror_verbose = EXTERR_VERBOSE_UNKNOWN; + +static void +exterr_verbose_init(void) +{ + /* + * No need to care about thread-safety, the result is + * idempotent. + */ + if (exterror_verbose != EXTERR_VERBOSE_UNKNOWN) + return; + if (issetugid()) { + exterror_verbose = EXTERR_VERBOSE_DEFAULT; + } else if (getenv(exterror_verbose_name) != NULL) { + exterror_verbose = EXTERR_VERBOSE_ALLOW; + } else { + exterror_verbose = EXTERR_VERBOSE_DEFAULT; + } +} int __uexterr_format(const struct uexterror *ue, char *buf, size_t bufsz) { + bool has_msg; + if (bufsz > UEXTERROR_MAXLEN) bufsz = UEXTERROR_MAXLEN; if (ue->error == 0) { strlcpy(buf, "", bufsz); return (0); } - if (ue->msg[0] == '\0') { - snprintf(buf, bufsz, - "errno %d category %u (src line %u) p1 %#jx p2 %#jx", - ue->error, ue->cat, ue->src_line, - (uintmax_t)ue->p1, (uintmax_t)ue->p2); + exterr_verbose_init(); + has_msg = ue->msg[0] != '\0'; + + if (has_msg) { + snprintf(buf, bufsz, ue->msg, (uintmax_t)ue->p1, + (uintmax_t)ue->p2); } else { - strlcpy(buf, ue->msg, bufsz); + strlcpy(buf, "", bufsz); + } + + if (exterror_verbose == EXTERR_VERBOSE_ALLOW || !has_msg) { + char lbuf[128]; + + snprintf(lbuf, sizeof(lbuf), + "errno %d category %u (src sys/%s:%u) p1 %#jx p2 %#jx", + ue->error, ue->cat, cat_to_filename(ue->cat), + ue->src_line, (uintmax_t)ue->p1, (uintmax_t)ue->p2); + if (has_msg) + strlcat(buf, " ", bufsz); + strlcat(buf, lbuf, bufsz); } return (0); } diff --git a/share/man/man7/environ.7 b/share/man/man7/environ.7 --- a/share/man/man7/environ.7 +++ b/share/man/man7/environ.7 @@ -114,6 +114,18 @@ .Xr ex 1 and .Xr vi 1 . +.It Ev EXTERROR_VERBOSE +Request the +.Xr err 3 +and +.Xr uexterr_gettext +functions to unconditionally report additional information, +mostly useful for the (kernel) developer to diagnose the issue. +See +.Xr err 3 +and +.Xr exterror 9 +for more details. .It Ev HOME A user's login directory, set by .Xr login 1 @@ -298,6 +310,7 @@ .Xr cd 1 , .Xr csh 1 , .Xr env 1 , +.Xr err 3 , .Xr ex 1 , .Xr login 1 , .Xr printenv 1 , @@ -311,7 +324,8 @@ .Xr system 3 , .Xr termcap 3 , .Xr termcap 5 , -.Xr simd 7 +.Xr simd 7 , +.Xr exterror 9 .Sh HISTORY The .Nm diff --git a/share/man/man9/exterror.9 b/share/man/man9/exterror.9 --- a/share/man/man9/exterror.9 +++ b/share/man/man9/exterror.9 @@ -90,6 +90,16 @@ .Fn EXTERROR macro can take two optional 64-bit integer arguments, whose meaning is specific to the subsystem. +The format string may include up to two printf-like format +specifiers to insert the optional argument values in the +user output, which is done in userspace. +.Pp +The format specifier must be for an integer type, and include the +.Dq j +format modifier to accept only the types +.Vt intmax_t +or +.Vt uintmax_t . .El .Pp The strings passed as the second argument are only retained diff --git a/sys/fs/fuse/fuse_device.c b/sys/fs/fuse/fuse_device.c --- a/sys/fs/fuse/fuse_device.c +++ b/sys/fs/fuse/fuse_device.c @@ -82,7 +82,7 @@ #include #include #include -#define EXTERR_CATEGORY EXTERR_CAT_FUSE +#define EXTERR_CATEGORY EXTERR_CAT_FUSE_DEVICE #include #include "fuse.h" diff --git a/sys/fs/fuse/fuse_vfsops.c b/sys/fs/fuse/fuse_vfsops.c --- a/sys/fs/fuse/fuse_vfsops.c +++ b/sys/fs/fuse/fuse_vfsops.c @@ -81,7 +81,7 @@ #include #include #include -#define EXTERR_CATEGORY EXTERR_CAT_FUSE +#define EXTERR_CATEGORY EXTERR_CAT_FUSE_VFS #include #include "fuse.h" diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c --- a/sys/fs/fuse/fuse_vnops.c +++ b/sys/fs/fuse/fuse_vnops.c @@ -89,7 +89,7 @@ #include #include #include -#define EXTERR_CATEGORY EXTERR_CAT_FUSE +#define EXTERR_CATEGORY EXTERR_CAT_FUSE_VNOPS #include #include diff --git a/sys/sys/exterr_cat.h b/sys/sys/exterr_cat.h --- a/sys/sys/exterr_cat.h +++ b/sys/sys/exterr_cat.h @@ -8,6 +8,17 @@ * under sponsorship from the FreeBSD Foundation. */ +/* + * The category identifiers for the extended errors. + * The ids participate in ABI between kernel and libc, so they must + * never be reused or changed. Only new ids can be added. + * + * After adding a new category id, run + * tools/build/make_libc_exterr_cat_filenames.sh + * from the top of the source tree, and commit updated file + * lib/libc/gen/exterr_cat_filenames.h + */ + #ifndef _SYS_EXTERR_CAT_H_ #define _SYS_EXTERR_CAT_H_ @@ -15,7 +26,7 @@ #define EXTERR_CAT_FILEDESC 2 #define EXTERR_KTRACE 3 /* To allow inclusion of this file into kern_ktrace.c */ -#define EXTERR_CAT_FUSE 4 +#define EXTERR_CAT_FUSE_VNOPS 4 #define EXTERR_CAT_INOTIFY 5 #define EXTERR_CAT_GENIO 6 #define EXTERR_CAT_BRIDGE 7 @@ -24,6 +35,8 @@ #define EXTERR_CAT_VFSBIO 10 #define EXTERR_CAT_GEOMVFS 11 #define EXTERR_CAT_GEOM 12 +#define EXTERR_CAT_FUSE_VFS 13 +#define EXTERR_CAT_FUSE_DEVICE 14 #endif diff --git a/sys/vm/vm_mmap.c b/sys/vm/vm_mmap.c --- a/sys/vm/vm_mmap.c +++ b/sys/vm/vm_mmap.c @@ -197,12 +197,14 @@ check_fp_fn = mrp->mr_check_fp_fn; if ((prot & ~(_PROT_ALL | PROT_MAX(_PROT_ALL))) != 0) { - return (EXTERROR(EINVAL, "unknown PROT bits")); + return (EXTERROR(EINVAL, "unknown PROT bits %#jx", prot)); } max_prot = PROT_MAX_EXTRACT(prot); prot = PROT_EXTRACT(prot); if (max_prot != 0 && (max_prot & prot) != prot) { - return (EXTERROR(ENOTSUP, "prot is not subset of max_prot")); + return (EXTERROR(ENOTSUP, + "prot %#jx is not subset of max_prot %#jx", + prot, max_prot)); } p = td->td_proc; @@ -236,7 +238,7 @@ if ((len == 0 && p->p_osrel >= P_OSREL_MAP_ANON) || ((flags & MAP_ANON) != 0 && (fd != -1 || pos != 0))) { return (EXTERROR(EINVAL, - "offset not zero/fd not -1 for MAP_ANON", + "offset %#jd not zero/fd %#jd not -1 for MAP_ANON", fd, pos)); } } else { @@ -247,8 +249,8 @@ if (flags & MAP_STACK) { if ((fd != -1) || ((prot & (PROT_READ | PROT_WRITE)) != (PROT_READ | PROT_WRITE))) { - return (EXTERROR(EINVAL, "MAP_STACK with prot < rw", - prot)); + return (EXTERROR(EINVAL, + "MAP_STACK with prot %#jx < rw", prot)); } flags |= MAP_ANON; pos = 0; @@ -257,18 +259,21 @@ MAP_STACK | MAP_NOSYNC | MAP_ANON | MAP_EXCL | MAP_NOCORE | MAP_PREFAULT_READ | MAP_GUARD | MAP_32BIT | MAP_ALIGNMENT_MASK)) != 0) { - return (EXTERROR(EINVAL, "reserved flag set")); + return (EXTERROR(EINVAL, "reserved flag set (flags %#jx)", + flags)); } if ((flags & (MAP_EXCL | MAP_FIXED)) == MAP_EXCL) { - return (EXTERROR(EINVAL, "EXCL without FIXED")); + return (EXTERROR(EINVAL, "EXCL without FIXED (flags %#jx)", + flags)); } if ((flags & (MAP_SHARED | MAP_PRIVATE)) == (MAP_SHARED | MAP_PRIVATE)) { - return (EXTERROR(EINVAL, "both SHARED and PRIVATE set")); + return (EXTERROR(EINVAL, + "both SHARED and PRIVATE set (flags %#jx)", flags)); } if (prot != PROT_NONE && (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) != 0) { - return (EXTERROR(EINVAL, "invalid prot", prot)); + return (EXTERROR(EINVAL, "invalid prot %#jx", prot)); } if ((flags & MAP_GUARD) != 0 && (prot != PROT_NONE || fd != -1 || pos != 0 || (flags & ~(MAP_FIXED | MAP_GUARD | MAP_EXCL | @@ -295,7 +300,7 @@ if (align != 0 && align != MAP_ALIGNED_SUPER && (align >> MAP_ALIGNMENT_SHIFT >= sizeof(void *) * NBBY || align >> MAP_ALIGNMENT_SHIFT < PAGE_SHIFT)) { - return (EXTERROR(EINVAL, "bad alignment", align)); + return (EXTERROR(EINVAL, "bad alignment %#jx", align)); } /* @@ -310,8 +315,8 @@ */ addr -= pageoff; if ((addr & PAGE_MASK) != 0) { - return (EXTERROR(EINVAL, "fixed mapping not aligned", - addr)); + return (EXTERROR(EINVAL, + "fixed mapping at %#jx not aligned", addr)); } /* Address range must be all in user VM space. */ @@ -321,7 +326,8 @@ } if (flags & MAP_32BIT && addr + size > MAP_32BIT_MAX_ADDR) { return (EXTERROR(EINVAL, - "fixed 32bit mapping does not fit into 4G")); + "fixed 32bit mapping of [%#jx %#jx] does not fit into 4G", + addr, addr + size)); } } else if (flags & MAP_32BIT) { /* @@ -1495,7 +1501,7 @@ handle, &foff, &object, &writecounted); break; default: - error = EXTERROR(EINVAL, "unsupported backing obj type", + error = EXTERROR(EINVAL, "unsupported backing obj type %jd", handle_type); break; } @@ -1578,7 +1584,7 @@ * exec). */ if ((foff & PAGE_MASK) != 0) { - return (EXTERROR(EINVAL, "offset not page-aligned", foff)); + return (EXTERROR(EINVAL, "offset %#jx not page-aligned", foff)); } if ((flags & MAP_FIXED) == 0) { @@ -1587,7 +1593,8 @@ } else { if (*addr != trunc_page(*addr)) { return (EXTERROR(EINVAL, - "non-fixed mapping address not aligned", *addr)); + "non-fixed mapping address %#jx not aligned", + *addr)); } fitit = false; } @@ -1599,7 +1606,7 @@ } if (foff != 0) { return (EXTERROR(EINVAL, - "anon mapping with non-zero offset")); + "anon mapping with non-zero offset %#jx", foff)); } docow = 0; } else if (flags & MAP_PREFAULT_READ) @@ -1702,6 +1709,6 @@ } if ((curthread->td_pflags2 & (TDP2_UEXTERR | TDP2_EXTERR)) == TDP2_UEXTERR) - EXTERROR(error, "mach error", rv); + EXTERROR(error, "mach error %jd", rv); return (error); } diff --git a/tests/sys/kern/exterr_test.c b/tests/sys/kern/exterr_test.c --- a/tests/sys/kern/exterr_test.c +++ b/tests/sys/kern/exterr_test.c @@ -51,7 +51,7 @@ ATF_CHECK_EQ(0, r); printf("Extended error: %s\n", exterr); /* Note: error string may need to be updated due to kernel changes */ - ATF_CHECK(strstr(exterr, "prot is not subset of max_prot") != 0); + ATF_CHECK(strstr(exterr, " is not subset of ") != 0); } ATF_TC(gettext_noextended); diff --git a/tools/build/make_libc_exterr_cat_filenames.sh b/tools/build/make_libc_exterr_cat_filenames.sh new file mode 100755 --- /dev/null +++ b/tools/build/make_libc_exterr_cat_filenames.sh @@ -0,0 +1,22 @@ +#!/bin/sh +set -e + +check="lib/libc/gen/uexterr_format.c" +target="lib/libc/gen/exterr_cat_filenames.h" + +if [ \! -f "${check}" ] ; then + echo "Script must be run from the top of the full source tree" + exit 1 +fi + +echo "/*" >"${target}" +printf " * Automatically %sgenerated, use\\n" \@ >>"${target}" +echo " * tools/build/make_libc_exterr_cat_filenames.sh" >>"${target}" +echo " */" >>"${target}" + +(find sys -type f -name '*.c' | \ + xargs grep -E '^#define[[:space:]]+EXTERR_CATEGORY[[:space:]]+EXTERR_CAT_' | \ + sed -E 's/[[:space:]]+/:/g' | \ + awk -F ':' '{filename = $1; sub(/^sys\//, "", filename); + printf("\t[%s] = \"%s\",\n", $4, filename)}') \ + >>"${target}"