[PATCH 3/3] Add pretty-printer for errno.

Zack Weinberg
2017-06-22T22:46:14Z

This patch adds the actual pretty-printer for errno. I could have
used Python's built-in errno module to get the symbolic names for the
constants, but it seemed better to do something entirely under our
control, so there's a .pysym file generated from errnos.texi, with a
hook that allows the Hurd to add additional constants. Then a .py
module is generated from that plus errno.h in the usual manner; many
thanks to the authors of the .pysym mechanism.

There is also a test which verifies that the .py file (not the .pysym
file) covers all of the constants defined in errno.h.

hurd-add-errno-constants.awk has been manually tested, but the
makefile logic that runs it has not been tested.

* stdlib/errno-printer.py: New pretty-printer.
* stdlib/test-errno-constants.py: New special test.
* stdlib/test-errno-printer.c, stdlib/test-errno-printer.py:
New pretty-printer test.
* stdlib/make-errno-constants.awk: New script to generate the
.pysym file needed by errno-printer.py.
* stdlib/Makefile: Install, run, and test all of the above, as
appropriate.

* sysdeps/mach/hurd/hurd-add-errno-constants.awk: New script to
add Mach/Hurd-specific errno constants to the .pysym file used by
stdlib/errno-printer.py.
* sysdeps/mach/hurd/Makefile: Hook hurd-add-errno-constants.awk
into the generation of that .pysym file.
---
stdlib/Makefile | 38 +++++++++
stdlib/errno-printer.py | 105 +++++++++++++++++++++++++
stdlib/make-errno-constants.awk | 66 ++++++++++++++++
stdlib/test-errno-constants.py | 58 ++++++++++++++
stdlib/test-errno-printer.c | 43 ++++++++++
stdlib/test-errno-printer.py | 71 +++++++++++++++++
sysdeps/mach/hurd/Makefile | 10 +++
sysdeps/mach/hurd/hurd-add-errno-constants.awk | 80 +++++++++++++++++++
8 files changed, 471 insertions(+)
create mode 100644 stdlib/errno-printer.py
create mode 100644 stdlib/make-errno-constants.awk
create mode 100644 stdlib/test-errno-constants.py
create mode 100644 stdlib/test-errno-printer.c
create mode 100644 stdlib/test-errno-printer.py
create mode 100644 sysdeps/mach/hurd/hurd-add-errno-constants.awk

diff --git a/stdlib/Makefile b/stdlib/Makefile
index 0314d5926b..31025465cb 100644
--- a/stdlib/Makefile
+++ b/stdlib/Makefile
@@ -149,6 +149,34 @@ ifeq ($(run-built-tests),yes)
tests-special += $(objpfx)tst-fmtmsg.out
endif

+ifdef PYTHON
+# Pretty-printer for errno. The .pysym file is itself generated from
+# errnos.texi, and can be augmented by sysdeps Makefiles, primarily for
+# the sake of the Hurd, which has a bunch of extra error constants.
+pretty-printers := errno-printer.py
+tests-printers := test-errno-printer
+gen-py-const-headers := errno_constants.pysym
+vpath %.pysym $(objpfx)
+
+define sysd-add-errno-constants
+:
+endef
+$(objpfx)errno_constants.pysym: make-errno-constants.awk \
+ $(..)manual/errno.texi
+ ($(AWK) -f make-errno-constants.awk $(..)manual/errno.texi; \
+ $(sysd-add-errno-constants)) > $@T
+ mv -f $@T $@
+
+# We must specify both CFLAGS and CPPFLAGS to override any
+# compiler options the user might have provided that conflict
+# with what we need e.g. user specifies CPPFLAGS with -O2 and
+# we need -O0.
+CFLAGS-test-errno-printer.c := $(CFLAGS-printers-tests)
+CPPFLAGS-test-errno-printer.c := $(CFLAGS-printers-tests)
+
+tests-special += $(objpfx)test-errno-constants.out
+endif
+
include ../Rules

ifeq ($(run-built-tests),yes)
@@ -220,3 +248,13 @@ $(objpfx)tst-setcontext3.out: tst-setcontext3.sh $(objpfx)tst-setcontext3
$(evaluate-test)

$(objpfx)tst-makecontext: $(libdl)
+
+# Note: errno_constants.py depends on errno.h and everything it
+# includes, as well as on errno_constants.pysym, so we don't need to
+# specify that this test also depends on both.
+$(objpfx)test-errno-constants.out: \
+ test-errno-constants.py $(objpfx)errno_constants.py
+ $(PYTHON) $< $(objpfx) $(CC) $(CFLAGS) $(CPPFLAGS) > $@; \
+ $(evaluate-test)
+
+libof-test-errno-constants = testsuite
diff --git a/stdlib/errno-printer.py b/stdlib/errno-printer.py
new file mode 100644
index 0000000000..aa09c78235
--- /dev/null
+++ b/stdlib/errno-printer.py
@@ -0,0 +1,105 @@
+# Pretty printer for errno.
+# Copyright (C) 2016-2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+"""This file contains the gdb pretty printers for the following types:
+
+ * __error_t (the type of 'errno')
+ * error_t (cast any 'int' to 'error_t' to print it like an errno value)
+
+You can check which printers are registered and enabled by issuing the
+'info pretty-printer' gdb command. Printers should trigger automatically when
+trying to print a variable of one of the types mentioned above.
+"""
+
+
+import gdb
+import gdb.printing
+import errno_constants
+
+
+def make_errno_reverse_mapping():
+ """Construct a reverse mapping from errno values to symbolic names.
+ The result is a dictionary indexed by integers, not a list,
+ because errno values are not necessarily contiguous.
+ """
+
+ # Certain errno symbols are allowed to have the same numeric value.
+ # If they do, one of them (whichever one is in POSIX, or if both or
+ # neither are, the shortest) is selected as the preferred name.
+ # This map goes from non-preferred name(s) to preferred name.
+ permitted_collisions = {
+ "EDEADLOCK": "EDEADLK",
+ "EOPNOTSUPP": "ENOTSUP",
+ "EWOULDBLOCK": "EAGAIN",
+ }
+
+ errno_names = { 0: "Success" }
+ for name in dir(errno_constants):
+ if name[0] == 'E':
+ number = getattr(errno_constants, name)
+ other = errno_names.get(number)
+ if other is None:
+ errno_names[number] = name
+ else:
+ p1 = permitted_collisions.get(name)
+ p2 = permitted_collisions.get(other)
+ if p1 is not None and p1 == other:
+ pass # the value in errno_names is already what we want
+ elif p2 is not None and p2 == name:
+ errno_names[number] = name
+ else:
+ raise RuntimeError(
+ "errno value collision: {} = {}, {}"
+ .format(number, name, errno_names[number]))
+
+ return errno_names
+
+
+errno_names = make_errno_reverse_mapping()
+
+
+class ErrnoPrinter(object):
+ """Pretty printer for errno values."""
+
+ def __init__(self, val):
+ self._val = int(val)
+
+ def to_string(self):
+ """gdb API function.
+
+ This is called from gdb when we try to print an error_t.
+ """
+ if self._val in errno_names:
+ return "{:d} ({})".format(self._val, errno_names[self._val])
+ else:
+ return "{:d}".format(self._val)
+
+
+def register(objfile):
+ """Register pretty printers for the current objfile."""
+
+ printer = gdb.printing.RegexpCollectionPrettyPrinter("glibc-errno")
+ printer.add_printer('error_t', r'^(?:__)?error_t', ErrnoPrinter)
+
+ if objfile == None:
+ objfile = gdb
+
+ gdb.printing.register_pretty_printer(objfile, printer)
+
+
+register(gdb.current_objfile())
diff --git a/stdlib/make-errno-constants.awk b/stdlib/make-errno-constants.awk
new file mode 100644
index 0000000000..32344dca95
--- /dev/null
+++ b/stdlib/make-errno-constants.awk
@@ -0,0 +1,66 @@
+# Copyright (C) 2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+# Generate errno_constants.pysym from errno.texi.
+# errno.texi contains lines like:
+# @errno{ENOSYS, 123, Function not implemented}
+# The number is only relevant for the Hurd.
+
+BEGIN {
+ print "#include <errno.h>"
+ print ""
+ print "-- Errno constants"
+
+ # Some error constants do not exist on all supported operating systems.
+ # FIXME: Encode this information in errno.texi.
+ ## (Sometimes) two names for the same number
+ optional["EDEADLOCK"] = 1
+ optional["EDEADLK"] = 1
+ optional["EOPNOTSUPP"] = 1
+ optional["ENOTSUP"] = 1
+ optional["EWOULDBLOCK"] = 1
+ optional["EAGAIN"] = 1
+ ## BSD-specific
+ optional["EAUTH"] = 1
+ optional["EBADRPC"] = 1
+ optional["EFTYPE"] = 1
+ optional["ENEEDAUTH"] = 1
+ optional["EPROCLIM"] = 1
+ optional["EPROCUNAVAIL"] = 1
+ optional["EPROGMISMATCH"] = 1
+ optional["EPROGUNAVAIL"] = 1
+ optional["ERPCMISMATCH"] = 1
+ ## GNU-specific
+ optional["EBACKGROUND"] = 1
+ optional["ED"] = 1
+ optional["EDIED"] = 1
+ optional["EGRATUITOUS"] = 1
+ optional["EGREGIOUS"] = 1
+ optional["EIEIO"] = 1
+}
+
+/^@errno\{/ {
+ e = substr($1, 8, length($1)-8)
+ if (e in optional)
+ {
+ print "#ifdef", e
+ print e
+ print "#endif"
+ }
+ else
+ print e
+}
diff --git a/stdlib/test-errno-constants.py b/stdlib/test-errno-constants.py
new file mode 100644
index 0000000000..a79df97625
--- /dev/null
+++ b/stdlib/test-errno-constants.py
@@ -0,0 +1,58 @@
+# Test that errno_constants.py includes every error number defined by errno.h.
+# Copyright (C) 2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+# Usage: test-errno-constants.py $(common-objpfx)stdlib $(CC) $(CFLAGS) $(CPPFLAGS)
+
+import os
+import sys
+import subprocess
+
+sys.path.append(sys.argv[1])
+import errno_constants
+
+def main():
+ cc_cmd = sys.argv[2:]
+ cc_cmd.extend(["-E", "-dM", "-xc", "-"])
+ cc_proc = subprocess.Popen(cc_cmd,
+ stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE)
+ cc_proc.stdin.write(b"#define _GNU_SOURCE\n"
+ b"#include <errno.h>\n")
+ cc_proc.stdin.close()
+
+ cc_output = cc_proc.stdout.read()
+ status = cc_proc.wait()
+ if status:
+ sys.stderr.write("{}\nunsuccessful exit, status {:04x}"
+ .format(" ".join(cc_cmd), status))
+ sys.exit(1)
+
+ ok = True
+ for line in cc_output.decode("utf-8").splitlines():
+ if not line.startswith("#define E"):
+ continue
+ emacro = line.split()[1]
+ if not hasattr(errno_constants, emacro):
+ if ok:
+ sys.stderr.write("*** Missing constants:\n")
+ ok = False
+ sys.stderr.write(emacro + "\n")
+
+ sys.exit(0 if ok else 1)
+
+main()
diff --git a/stdlib/test-errno-printer.c b/stdlib/test-errno-printer.c
new file mode 100644
index 0000000000..da2345f188
--- /dev/null
+++ b/stdlib/test-errno-printer.c
@@ -0,0 +1,43 @@
+/* Helper program for testing the errno pretty-printer.
+ Copyright (C) 2017 Free Software Foundation, Inc.
+ This file is part of the GNU C Library.
+
+ The GNU C Library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ The GNU C Library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with the GNU C Library; if not, see
+ <http://www.gnu.org/licenses/>. */
+
+#define _GNU_SOURCE 1
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+
+#define PASS 0
+#define FAIL 1
+
+const error_t array_of_error_t[3] = { 0, ERANGE, -2 };
+
+__thread int ensure_gdb_can_read_thread_variables = 0;
+
+int
+main (void)
+{
+ int result = PASS;
+ errno = array_of_error_t[0];
+ unsigned long x = strtoul("9999999999999999999999999999999999999", 0, 10);
+ if (x != ULONG_MAX)
+ result = FAIL;
+ if (errno != ERANGE)
+ result = FAIL;
+ errno = -2; /* Break: test errno 2 */
+ return result;
+}
diff --git a/stdlib/test-errno-printer.py b/stdlib/test-errno-printer.py
new file mode 100644
index 0000000000..b55ee08b08
--- /dev/null
+++ b/stdlib/test-errno-printer.py
@@ -0,0 +1,71 @@
+# Test for the errno pretty-printer.
+# Copyright (C) 2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+import sys
+
+from test_printers_common import *
+
+test_source = sys.argv[1]
+test_bin = sys.argv[2]
+printer_files = sys.argv[3:]
+printer_names = ['global glibc-errno']
+
+try:
+ init_test(test_bin, printer_files, printer_names)
+ go_to_main()
+
+ # All supported versions of gdb do run the pretty-printer on an
+ # _array_ of error_t.
+ test_printer('array_of_error_t',
+ r'= \{0 \(Success\), \d+ \(ERANGE\), -2\}', is_ptr=False)
+
+ # Some versions of gdb don't run the pretty-printer on a _scalar_
+ # whose type is error_t. If we have such a gdb, the test is
+ # unsupported.
+ test('print (error_t) 0',
+ pattern = r'= 0 \(Success\)$',
+ unsupported_pattern = r'= 0$')
+
+ # Some versions of gdb don't support reading thread-specific variables;
+ # these versions may also have trouble reading errno.
+ test('print ensure_gdb_can_read_thread_variables',
+ pattern = r'= 0$',
+ unsupported_pattern = r'Cannot find thread-local')
+
+ next_cmd()
+ next_cmd()
+ test_printer('errno', r'0 (Success)', is_ptr=False)
+ next_cmd()
+ test_printer('errno', r'\d+ (ERANGE)', is_ptr=False)
+
+ break_at(test_source, 'test errno 2', is_ptr=False)
+ continue_cmd()
+ next_cmd()
+ test_printer('errno', r'-2', is_ptr=False)
+
+ continue_cmd() # Exit
+
+except (NoLineError, pexpect.TIMEOUT) as exception:
+ print('Error: {0}'.format(exception))
+ result = FAIL
+
+else:
+ print('Test succeeded.')
+ result = PASS
+
+exit(result)
diff --git a/sysdeps/mach/hurd/Makefile b/sysdeps/mach/hurd/Makefile
index 13bdf5c7c9..5171425650 100644
--- a/sysdeps/mach/hurd/Makefile
+++ b/sysdeps/mach/hurd/Makefile
@@ -98,6 +98,16 @@ $(common-objpfx)stamp-errnos: $(hurd)/errnos.awk $(errno.texinfo) \
touch $@

common-generated += errnos.d stamp-errnos
+
+# Augmentations to the errno pretty-printer.
+ifeq ($(subdir),stdlib)
+define sysd-add-errno-constants
+$(AWK) -f $(hurd)/hurd-add-errno-constants.awk $(mach-errnos-deps)
+endef
+$(objpfx)errno_constants.pysym: \
+ $(hurd)/hurd-add-errno-constants.awk $(mach-errnos-deps)
+endif
+
\f
# We install the real libc.a as libcrt.a and as libc.a we install a linker
# script which does -( -lcrt -lmachuser -lhurduser -).
diff --git a/sysdeps/mach/hurd/hurd-add-errno-constants.awk b/sysdeps/mach/hurd/hurd-add-errno-constants.awk
new file mode 100644
index 0000000000..0d51766c97
--- /dev/null
+++ b/sysdeps/mach/hurd/hurd-add-errno-constants.awk
@@ -0,0 +1,80 @@
+# Add Hurd-specific constants to errno_constants.pysym.
+# Copyright (C) 2017 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <http://www.gnu.org/licenses/>.
+
+# On the Hurd, errno.h defines E-constants corresponding to a number of
+# Mach low-level errors. Add these to the set of values recognized by
+# stdlib/errno-printer.py. This script must be kept in sync with errnos.awk.
+
+BEGIN {
+ in_mach_errors = "";
+ in_mig_errors = 0;
+ in_device_errors = 0;
+}
+
+function emit_subhead()
+{
+ header = FILENAME;
+ sub(/.*include\//, "", header);
+ printf("\n-- Errors from <%s>\n", header);
+}
+
+NF == 3 && $1 == "#define" && $2 == "MACH_SEND_IN_PROGRESS" \
+ {
+ in_mach_errors = FILENAME;
+ emit_subhead();
+ }
+NF == 3 && $1 == "#define" && $2 == "KERN_SUCCESS" \
+ {
+ in_mach_errors = FILENAME;
+ emit_subhead();
+ next;
+ }
+in_mach_errors != "" && $2 == "MACH_IPC_COMPAT" \
+ {
+ in_mach_errors = "";
+ next;
+ }
+
+$1 == "#define" && $2 == "_MACH_MIG_ERRORS_H_" \
+ {
+ in_mig_errors = 1;
+ emit_subhead();
+ next;
+ }
+in_mig_errors && $1 == "#endif" && $3 == "_MACH_MIG_ERRORS_H_" \
+ {
+ in_mig_errors = 0;
+ }
+
+$1 == "#define" && $2 == "D_SUCCESS" \
+ {
+ in_device_errors = 1;
+ emit_subhead();
+ next;
+ }
+in_device_errors && $1 == "#endif" \
+ {
+ in_device_errors = 0;
+ }
+
+(in_mach_errors == FILENAME && NF == 3 && $1 == "#define") || \
+(in_mig_errors && $1 == "#define" && $3 <= -300) || \
+(in_device_errors && $1 == "#define" && $2 ~ /D_/ && NF > 3) \
+ {
+ print "E" $2;
+ }
--
2.11.0