changeset 632:dfbcf2a05902

error: allow hooking xstrerror errno mapping Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Mon, 05 Nov 2018 18:36:30 -0500
parents 744982e99bbc
children 912549732d42
files error.c include/jeffpc/jeffpc.h init.c tests/CMakeLists.txt tests/test_xstrerror.c
diffstat 5 files changed, 165 insertions(+), 1 deletions(-) [+]
line wrap: on
line diff
--- a/error.c	Thu Nov 08 22:52:40 2018 -0500
+++ b/error.c	Mon Nov 05 18:36:30 2018 -0500
@@ -303,6 +303,14 @@
 
 const char *xstrerror(int e)
 {
+	if (libops.strerror) {
+		const char *ret;
+
+		ret = libops.strerror(e);
+		if (ret)
+			return ret;
+	}
+
 	switch (e) {
 		case 0:
 			return "Success";
--- a/include/jeffpc/jeffpc.h	Thu Nov 08 22:52:40 2018 -0500
+++ b/include/jeffpc/jeffpc.h	Mon Nov 05 18:36:30 2018 -0500
@@ -32,6 +32,7 @@
 	void (*assfail3)(const char *a, uintmax_t lv, const char *op,
 			 uintmax_t rv, const char *f, int l, const char *fxn);
 	const char *(*get_session)(void);
+	const char *(*strerror)(int);
 };
 
 extern void jeffpc_init(struct jeffpc_ops *ops);
--- a/init.c	Thu Nov 08 22:52:40 2018 -0500
+++ b/init.c	Mon Nov 05 18:36:30 2018 -0500
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2017 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ * Copyright (c) 2016-2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -55,4 +55,5 @@
 	SET_OP(assfail,     default_assfail);
 	SET_OP(assfail3,    default_assfail3);
 	SET_OP(get_session, NULL);
+	SET_OP(strerror,    NULL);
 }
--- a/tests/CMakeLists.txt	Thu Nov 08 22:52:40 2018 -0500
+++ b/tests/CMakeLists.txt	Mon Nov 05 18:36:30 2018 -0500
@@ -64,3 +64,4 @@
 build_test_bin_and_run(utf8-to-utf32)
 build_test_bin_and_run(uuid)
 build_test_bin_and_run(version)
+build_test_bin_and_run(xstrerror)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_xstrerror.c	Mon Nov 05 18:36:30 2018 -0500
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2018 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <stdbool.h>
+
+#include <jeffpc/error.h>
+
+#include "test.c"
+
+/*
+ * The callback can either return a string (which gets propagated to the
+ * xstrerror caller) or return NULL (which lets generic xstrerror code
+ * handle the errno).  We want to test both behaviors.  If this variable is
+ * set to true, our callback function returns NULL.  Otherwise, it returns a
+ * mangled int (see below).
+ */
+static bool return_null;
+
+/*
+ * Essentially, we want to make it obvious if the test failed because
+ * xstrerror returned:
+ *
+ *  - NULL (bad)
+ *  - a negated errno (bad)
+ *  - a real string (bad)
+ *  - a real-looking pointer (good)
+ *
+ * The easiest way to do that is to "reserve" a unique pointer for each
+ * possible return string.
+ */
+static char error_ptrs[MAX_ERRNO + 1];
+
+static inline void *mangle_int(int i)
+{
+	ASSERT3S(i, <=, 0);
+	ASSERT3S(i, >=, -MAX_ERRNO);
+
+	return &error_ptrs[-i];
+}
+
+static inline bool is_mangled(const char *ptr)
+{
+	return (ptr >= &error_ptrs[0]) && (ptr <= &error_ptrs[MAX_ERRNO]);
+}
+
+static const char *mystrerror(int e)
+{
+	fprintf(stderr, "called with %d...", e);
+
+	return return_null ? NULL : mangle_int(e);
+}
+
+static void __check_err(int e, const char *got)
+{
+	if (return_null) {
+		if (got == NULL)
+			fail("expected a string, got NULL");
+		if (IS_ERR(got))
+			fail("expected a string, got errno %d (%p)",
+			     PTR_ERR(got), got);
+		if (is_mangled(got))
+			fail("expected a string, got mangled ptr");
+
+		/*
+		 * It'd be nice if we could check that the string is sane,
+		 * but we don't know what exact string to expect.
+		 */
+	} else {
+		void *exp = mangle_int(e);
+
+		if (got != exp)
+			fail("expected %p, got %p", exp, got);
+	}
+}
+
+static void __check_ok(int e, const char *got)
+{
+	if (return_null) {
+		if (got == NULL)
+			fail("expected 'Success', got NULL");
+		if (IS_ERR(got))
+			fail("expected 'Success', got errno %d (%p)",
+			     PTR_ERR(got), got);
+		if (strcmp(got, "Success"))
+			fail("expected 'Success', got '%s'", got);
+	} else {
+		void *exp = mangle_int(0);
+
+		if (got != exp)
+			fail("expected %p, got %p", exp, got);
+	}
+}
+
+static void __test(int e, void (*check)(int, const char *))
+{
+	const char *got;
+
+	fprintf(stderr, "%5d %s...", e, return_null ? "null" : "mangled");
+
+	got = xstrerror(e);
+	fprintf(stderr, "returned %p...", got);
+
+	check(e, got);
+
+	fprintf(stderr, "ok.\n");
+}
+
+static void do_test(void)
+{
+	int i;
+
+	/* test errors */
+	for (i = -MAX_ERRNO + 1; i < 0; i++)
+		__test(i, __check_err);
+
+	/* test success */
+	__test(0, __check_ok);
+}
+
+void test(void)
+{
+	struct jeffpc_ops init_ops = {
+		.strerror = mystrerror,
+	};
+
+	/* override the ops set by the generic code */
+	jeffpc_init(&init_ops);
+
+	return_null = false;
+	do_test();
+
+	return_null = true;
+	do_test();
+}