printf: add support for printing symbolic error names
authorRasmus Villemoes <linux@rasmusvillemoes.dk>
Tue, 15 Oct 2019 19:07:05 +0000 (21:07 +0200)
committerPetr Mladek <pmladek@suse.com>
Thu, 17 Oct 2019 14:23:25 +0000 (16:23 +0200)
It has been suggested several times to extend vsnprintf() to be able
to convert the numeric value of ENOSPC to print "ENOSPC". This
implements that as a %p extension: With %pe, one can do

  if (IS_ERR(foo)) {
    pr_err("Sorry, can't do that: %pe\n", foo);
    return PTR_ERR(foo);
  }

instead of what is seen in quite a few places in the kernel:

  if (IS_ERR(foo)) {
    pr_err("Sorry, can't do that: %ld\n", PTR_ERR(foo));
    return PTR_ERR(foo);
  }

If the value passed to %pe is an ERR_PTR, but the library function
errname() added here doesn't know about the value, the value is simply
printed in decimal. If the value passed to %pe is not an ERR_PTR, we
treat it as an ordinary %p and thus print the hashed value (passing
non-ERR_PTR values to %pe indicates a bug in the caller, but we can't
do much about that).

With my embedded hat on, and because it's not very invasive to do,
I've made it possible to remove this. The errname() function and
associated lookup tables take up about 3K. For most, that's probably
quite acceptable and a price worth paying for more readable
dmesg (once this starts getting used), while for those that disable
printk() it's of very little use - I don't see a
procfs/sysfs/seq_printf() file reasonably making use of this - and
they clearly want to squeeze vmlinux as much as possible. Hence the
default y if PRINTK.

The symbols to include have been found by massaging the output of

  find arch include -iname 'errno*.h' | xargs grep -E 'define\s*E'

In the cases where some common aliasing exists
(e.g. EAGAIN=EWOULDBLOCK on all platforms, EDEADLOCK=EDEADLK on most),
I've moved the more popular one (in terms of 'git grep -w Efoo | wc)
to the bottom so that one takes precedence.

Link: http://lkml.kernel.org/r/20191015190706.15989-1-linux@rasmusvillemoes.dk
To: "Jonathan Corbet" <corbet@lwn.net>
To: linux-kernel@vger.kernel.org
Cc: "Andy Shevchenko" <andy.shevchenko@gmail.com>
Cc: "Andrew Morton" <akpm@linux-foundation.org>
Cc: "Joe Perches" <joe@perches.com>
Cc: linux-doc@vger.kernel.org
Signed-off-by: Rasmus Villemoes <linux@rasmusvillemoes.dk>
Acked-by: Uwe Kleine-König <uwe@kleine-koenig.org>
Reviewed-by: Petr Mladek <pmladek@suse.com>
[andy.shevchenko@gmail.com: use abs()]
Acked-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Petr Mladek <pmladek@suse.com>
Documentation/core-api/printk-formats.rst
include/linux/errname.h [new file with mode: 0644]
lib/Kconfig.debug
lib/Makefile
lib/errname.c [new file with mode: 0644]
lib/test_printf.c
lib/vsprintf.c

index 75d2bbe9813f0aae8bedef0e0429b5dfbab2e9e7..dffd5c0b24f8858c70a5e3e9dbf3e6829b683de0 100644 (file)
@@ -79,6 +79,18 @@ has the added benefit of providing a unique identifier. On 64-bit machines
 the first 32 bits are zeroed. The kernel will print ``(ptrval)`` until it
 gathers enough entropy. If you *really* want the address see %px below.
 
+Error Pointers
+--------------
+
+::
+
+       %pe     -ENOSPC
+
+For printing error pointers (i.e. a pointer for which IS_ERR() is true)
+as a symbolic error name. Error values for which no symbolic name is
+known are printed in decimal, while a non-ERR_PTR passed as the
+argument to %pe gets treated as ordinary %p.
+
 Symbols/Function Pointers
 -------------------------
 
diff --git a/include/linux/errname.h b/include/linux/errname.h
new file mode 100644 (file)
index 0000000..e8576ad
--- /dev/null
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_ERRNAME_H
+#define _LINUX_ERRNAME_H
+
+#include <linux/stddef.h>
+
+#ifdef CONFIG_SYMBOLIC_ERRNAME
+const char *errname(int err);
+#else
+static inline const char *errname(int err)
+{
+       return NULL;
+}
+#endif
+
+#endif /* _LINUX_ERRNAME_H */
index 06d9c9d70385f1d75c72b80d91edf2cc0cf24510..4934b69c7b3900b9293f7a65773f523eefa13402 100644 (file)
@@ -164,6 +164,15 @@ config DYNAMIC_DEBUG
          See Documentation/admin-guide/dynamic-debug-howto.rst for additional
          information.
 
+config SYMBOLIC_ERRNAME
+       bool "Support symbolic error names in printf"
+       default y if PRINTK
+       help
+         If you say Y here, the kernel's printf implementation will
+         be able to print symbolic error names such as ENOSPC instead
+         of the number 28. It makes the kernel image slightly larger
+         (about 3KB), but can make the kernel logs easier to read.
+
 endmenu # "printk and dmesg options"
 
 menu "Compile-time checks and compiler options"
index d3daedf93c5a7275d1e262f31ed5e3ae3d13ccd6..907759f28916b841ec2797e40b66a8697cb1c11b 100644 (file)
@@ -183,6 +183,7 @@ lib-$(CONFIG_GENERIC_BUG) += bug.o
 obj-$(CONFIG_HAVE_ARCH_TRACEHOOK) += syscall.o
 
 obj-$(CONFIG_DYNAMIC_DEBUG) += dynamic_debug.o
+obj-$(CONFIG_SYMBOLIC_ERRNAME) += errname.o
 
 obj-$(CONFIG_NLATTR) += nlattr.o
 
diff --git a/lib/errname.c b/lib/errname.c
new file mode 100644 (file)
index 0000000..0c4d3e6
--- /dev/null
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/build_bug.h>
+#include <linux/errno.h>
+#include <linux/errname.h>
+#include <linux/kernel.h>
+
+/*
+ * Ensure these tables do not accidentally become gigantic if some
+ * huge errno makes it in. On most architectures, the first table will
+ * only have about 140 entries, but mips and parisc have more sparsely
+ * allocated errnos (with EHWPOISON = 257 on parisc, and EDQUOT = 1133
+ * on mips), so this wastes a bit of space on those - though we
+ * special case the EDQUOT case.
+ */
+#define E(err) [err + BUILD_BUG_ON_ZERO(err <= 0 || err > 300)] = "-" #err
+static const char *names_0[] = {
+       E(E2BIG),
+       E(EACCES),
+       E(EADDRINUSE),
+       E(EADDRNOTAVAIL),
+       E(EADV),
+       E(EAFNOSUPPORT),
+       E(EALREADY),
+       E(EBADE),
+       E(EBADF),
+       E(EBADFD),
+       E(EBADMSG),
+       E(EBADR),
+       E(EBADRQC),
+       E(EBADSLT),
+       E(EBFONT),
+       E(EBUSY),
+#ifdef ECANCELLED
+       E(ECANCELLED),
+#endif
+       E(ECHILD),
+       E(ECHRNG),
+       E(ECOMM),
+       E(ECONNABORTED),
+       E(ECONNRESET),
+       E(EDEADLOCK),
+       E(EDESTADDRREQ),
+       E(EDOM),
+       E(EDOTDOT),
+#ifndef CONFIG_MIPS
+       E(EDQUOT),
+#endif
+       E(EEXIST),
+       E(EFAULT),
+       E(EFBIG),
+       E(EHOSTDOWN),
+       E(EHOSTUNREACH),
+       E(EHWPOISON),
+       E(EIDRM),
+       E(EILSEQ),
+#ifdef EINIT
+       E(EINIT),
+#endif
+       E(EINPROGRESS),
+       E(EINTR),
+       E(EINVAL),
+       E(EIO),
+       E(EISCONN),
+       E(EISDIR),
+       E(EISNAM),
+       E(EKEYEXPIRED),
+       E(EKEYREJECTED),
+       E(EKEYREVOKED),
+       E(EL2HLT),
+       E(EL2NSYNC),
+       E(EL3HLT),
+       E(EL3RST),
+       E(ELIBACC),
+       E(ELIBBAD),
+       E(ELIBEXEC),
+       E(ELIBMAX),
+       E(ELIBSCN),
+       E(ELNRNG),
+       E(ELOOP),
+       E(EMEDIUMTYPE),
+       E(EMFILE),
+       E(EMLINK),
+       E(EMSGSIZE),
+       E(EMULTIHOP),
+       E(ENAMETOOLONG),
+       E(ENAVAIL),
+       E(ENETDOWN),
+       E(ENETRESET),
+       E(ENETUNREACH),
+       E(ENFILE),
+       E(ENOANO),
+       E(ENOBUFS),
+       E(ENOCSI),
+       E(ENODATA),
+       E(ENODEV),
+       E(ENOENT),
+       E(ENOEXEC),
+       E(ENOKEY),
+       E(ENOLCK),
+       E(ENOLINK),
+       E(ENOMEDIUM),
+       E(ENOMEM),
+       E(ENOMSG),
+       E(ENONET),
+       E(ENOPKG),
+       E(ENOPROTOOPT),
+       E(ENOSPC),
+       E(ENOSR),
+       E(ENOSTR),
+#ifdef ENOSYM
+       E(ENOSYM),
+#endif
+       E(ENOSYS),
+       E(ENOTBLK),
+       E(ENOTCONN),
+       E(ENOTDIR),
+       E(ENOTEMPTY),
+       E(ENOTNAM),
+       E(ENOTRECOVERABLE),
+       E(ENOTSOCK),
+       E(ENOTTY),
+       E(ENOTUNIQ),
+       E(ENXIO),
+       E(EOPNOTSUPP),
+       E(EOVERFLOW),
+       E(EOWNERDEAD),
+       E(EPERM),
+       E(EPFNOSUPPORT),
+       E(EPIPE),
+#ifdef EPROCLIM
+       E(EPROCLIM),
+#endif
+       E(EPROTO),
+       E(EPROTONOSUPPORT),
+       E(EPROTOTYPE),
+       E(ERANGE),
+       E(EREMCHG),
+#ifdef EREMDEV
+       E(EREMDEV),
+#endif
+       E(EREMOTE),
+       E(EREMOTEIO),
+#ifdef EREMOTERELEASE
+       E(EREMOTERELEASE),
+#endif
+       E(ERESTART),
+       E(ERFKILL),
+       E(EROFS),
+#ifdef ERREMOTE
+       E(ERREMOTE),
+#endif
+       E(ESHUTDOWN),
+       E(ESOCKTNOSUPPORT),
+       E(ESPIPE),
+       E(ESRCH),
+       E(ESRMNT),
+       E(ESTALE),
+       E(ESTRPIPE),
+       E(ETIME),
+       E(ETIMEDOUT),
+       E(ETOOMANYREFS),
+       E(ETXTBSY),
+       E(EUCLEAN),
+       E(EUNATCH),
+       E(EUSERS),
+       E(EXDEV),
+       E(EXFULL),
+
+       E(ECANCELED), /* ECANCELLED */
+       E(EAGAIN), /* EWOULDBLOCK */
+       E(ECONNREFUSED), /* EREFUSED */
+       E(EDEADLK), /* EDEADLOCK */
+};
+#undef E
+
+#define E(err) [err - 512 + BUILD_BUG_ON_ZERO(err < 512 || err > 550)] = "-" #err
+static const char *names_512[] = {
+       E(ERESTARTSYS),
+       E(ERESTARTNOINTR),
+       E(ERESTARTNOHAND),
+       E(ENOIOCTLCMD),
+       E(ERESTART_RESTARTBLOCK),
+       E(EPROBE_DEFER),
+       E(EOPENSTALE),
+       E(ENOPARAM),
+
+       E(EBADHANDLE),
+       E(ENOTSYNC),
+       E(EBADCOOKIE),
+       E(ENOTSUPP),
+       E(ETOOSMALL),
+       E(ESERVERFAULT),
+       E(EBADTYPE),
+       E(EJUKEBOX),
+       E(EIOCBQUEUED),
+       E(ERECALLCONFLICT),
+};
+#undef E
+
+static const char *__errname(unsigned err)
+{
+       if (err < ARRAY_SIZE(names_0))
+               return names_0[err];
+       if (err >= 512 && err - 512 < ARRAY_SIZE(names_512))
+               return names_512[err - 512];
+       /* But why? */
+       if (IS_ENABLED(CONFIG_MIPS) && err == EDQUOT) /* 1133 */
+               return "-EDQUOT";
+       return NULL;
+}
+
+/*
+ * errname(EIO) -> "EIO"
+ * errname(-EIO) -> "-EIO"
+ */
+const char *errname(int err)
+{
+       const char *name = __errname(abs(err));
+       if (!name)
+               return NULL;
+
+       return err > 0 ? name + 1 : name;
+}
index 5d94cbff2120f9a09e0f67cd21289e22b1537df9..030daeb4fe2117f3562f2be8b492ce45f11f5d1d 100644 (file)
@@ -593,6 +593,26 @@ flags(void)
        kfree(cmp_buffer);
 }
 
+static void __init
+errptr(void)
+{
+       test("-1234", "%pe", ERR_PTR(-1234));
+
+       /* Check that %pe with a non-ERR_PTR gets treated as ordinary %p. */
+       BUILD_BUG_ON(IS_ERR(PTR));
+       test_hashed("%pe", PTR);
+
+#ifdef CONFIG_SYMBOLIC_ERRNAME
+       test("(-ENOTSOCK)", "(%pe)", ERR_PTR(-ENOTSOCK));
+       test("(-EAGAIN)", "(%pe)", ERR_PTR(-EAGAIN));
+       BUILD_BUG_ON(EAGAIN != EWOULDBLOCK);
+       test("(-EAGAIN)", "(%pe)", ERR_PTR(-EWOULDBLOCK));
+       test("[-EIO    ]", "[%-8pe]", ERR_PTR(-EIO));
+       test("[    -EIO]", "[%8pe]", ERR_PTR(-EIO));
+       test("-EPROBE_DEFER", "%pe", ERR_PTR(-EPROBE_DEFER));
+#endif
+}
+
 static void __init
 test_pointer(void)
 {
@@ -615,6 +635,7 @@ test_pointer(void)
        bitmap();
        netdev_features();
        flags();
+       errptr();
 }
 
 static void __init selftest(void)
index e78017a3e1bd235621574f5f4dd5acc050bb26d7..b54d252b398e0b0239f409c6b73ae03fe3f2768a 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/build_bug.h>
 #include <linux/clk.h>
 #include <linux/clk-provider.h>
+#include <linux/errname.h>
 #include <linux/module.h>      /* for KSYM_SYMBOL_LEN */
 #include <linux/types.h>
 #include <linux/string.h>
@@ -613,6 +614,25 @@ static char *string_nocheck(char *buf, char *end, const char *s,
        return widen_string(buf, len, end, spec);
 }
 
+static char *err_ptr(char *buf, char *end, void *ptr,
+                    struct printf_spec spec)
+{
+       int err = PTR_ERR(ptr);
+       const char *sym = errname(err);
+
+       if (sym)
+               return string_nocheck(buf, end, sym, spec);
+
+       /*
+        * Somebody passed ERR_PTR(-1234) or some other non-existing
+        * Efoo - or perhaps CONFIG_SYMBOLIC_ERRNAME=n. Fall back to
+        * printing it as its decimal representation.
+        */
+       spec.flags |= SIGN;
+       spec.base = 10;
+       return number(buf, end, err, spec);
+}
+
 /* Be careful: error messages must fit into the given buffer. */
 static char *error_string(char *buf, char *end, const char *s,
                          struct printf_spec spec)
@@ -2187,6 +2207,11 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
                return kobject_string(buf, end, ptr, spec, fmt);
        case 'x':
                return pointer_string(buf, end, ptr, spec);
+       case 'e':
+               /* %pe with a non-ERR_PTR gets treated as plain %p */
+               if (!IS_ERR(ptr))
+                       break;
+               return err_ptr(buf, end, ptr, spec);
        }
 
        /* default is to _not_ leak addresses, hash before printing */
@@ -2823,6 +2848,7 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt, va_list args)
                        case 'f':
                        case 'x':
                        case 'K':
+                       case 'e':
                                save_arg(void *);
                                break;
                        default:
@@ -2999,6 +3025,7 @@ int bstr_printf(char *buf, size_t size, const char *fmt, const u32 *bin_buf)
                        case 'f':
                        case 'x':
                        case 'K':
+                       case 'e':
                                process = true;
                                break;
                        default: