summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2025-02-09 15:04:27 +0100
committerMattias Andrée <m@maandree.se>2025-02-09 15:04:27 +0100
commited004cba0e8d1d383def76f795b1e63ba0aaa89a (patch)
treeb12e5f23329f631b66c19b932551e4dff5aa477f
downloadliblog-ed004cba0e8d1d383def76f795b1e63ba0aaa89a.tar.gz
liblog-ed004cba0e8d1d383def76f795b1e63ba0aaa89a.tar.bz2
liblog-ed004cba0e8d1d383def76f795b1e63ba0aaa89a.tar.xz
First commit (everything was written 2024)
Signed-off-by: Mattias Andrée <m@maandree.se>
-rw-r--r--.gitignore16
-rw-r--r--LICENSE15
-rw-r--r--Makefile182
-rw-r--r--TODO2
-rw-r--r--common.h316
-rw-r--r--config.mk8
-rw-r--r--liblog.h1583
-rw-r--r--liblog_alert.c23
-rw-r--r--liblog_alert_cork.c23
-rw-r--r--liblog_apply_env_mask.c22
-rw-r--r--liblog_clear_mask.c29
-rw-r--r--liblog_critical.c23
-rw-r--r--liblog_critical_cork.c23
-rw-r--r--liblog_debug.c23
-rw-r--r--liblog_debug_cork.c23
-rw-r--r--liblog_destroy_context.c32
-rw-r--r--liblog_destroy_output.c40
-rw-r--r--liblog_dump_backtrace.c25
-rw-r--r--liblog_dump_backtrace_cork.c25
-rw-r--r--liblog_emergency.c23
-rw-r--r--liblog_emergency_cork.c23
-rw-r--r--liblog_error.c23
-rw-r--r--liblog_error_cork.c23
-rw-r--r--liblog_flush__.c18
-rw-r--r--liblog_info.c23
-rw-r--r--liblog_info_cork.c23
-rw-r--r--liblog_init_context.c24
-rw-r--r--liblog_log.c25
-rw-r--r--liblog_log_cork.c25
-rw-r--r--liblog_log_no_backtrace.c25
-rw-r--r--liblog_log_no_backtrace_cork.c25
-rw-r--r--liblog_logmask__.c198
-rw-r--r--liblog_mask_level.c113
-rw-r--r--liblog_mask_range.c412
-rw-r--r--liblog_mask_verbose.c111
-rw-r--r--liblog_notice.c23
-rw-r--r--liblog_notice_cork.c23
-rw-r--r--liblog_trace.c23
-rw-r--r--liblog_trace__.c25
-rw-r--r--liblog_trace_cork.c23
-rw-r--r--liblog_uncork.c25
-rw-r--r--liblog_unmask_level.c113
-rw-r--r--liblog_unmask_range.c412
-rw-r--r--liblog_unmask_verbose.c111
-rw-r--r--liblog_use_fd.c11
-rw-r--r--liblog_use_fd_for_range.c12
-rw-r--r--liblog_use_file.c32
-rw-r--r--liblog_use_file_for_range.c33
-rw-r--r--liblog_use_output.c54
-rw-r--r--liblog_use_stderr.c11
-rw-r--r--liblog_use_stderr_for_range.c12
-rw-r--r--liblog_use_stream.c11
-rw-r--r--liblog_use_stream_for_range.c12
-rw-r--r--liblog_use_syslog.c99
-rw-r--r--liblog_use_syslog_for_range.c12
-rw-r--r--liblog_valert.c23
-rw-r--r--liblog_valert_cork.c23
-rw-r--r--liblog_vcritical.c23
-rw-r--r--liblog_vcritical_cork.c23
-rw-r--r--liblog_vdebug.c23
-rw-r--r--liblog_vdebug_cork.c23
-rw-r--r--liblog_vemergency.c23
-rw-r--r--liblog_vemergency_cork.c23
-rw-r--r--liblog_verror.c23
-rw-r--r--liblog_verror_cork.c23
-rw-r--r--liblog_vinfo.c23
-rw-r--r--liblog_vinfo_cork.c23
-rw-r--r--liblog_vlog.c25
-rw-r--r--liblog_vlog_cork.c25
-rw-r--r--liblog_vlog_no_backtrace.c25
-rw-r--r--liblog_vlog_no_backtrace_cork.c25
-rw-r--r--liblog_vnotice.c23
-rw-r--r--liblog_vnotice_cork.c23
-rw-r--r--liblog_vtrace.c23
-rw-r--r--liblog_vtrace_cork.c23
-rw-r--r--liblog_vwarning.c23
-rw-r--r--liblog_vwarning_cork.c23
-rw-r--r--liblog_vxlog.c121
-rw-r--r--liblog_warning.c23
-rw-r--r--liblog_warning_cork.c23
-rw-r--r--liblog_whence__.c24
-rw-r--r--liblog_xlog.c25
-rw-r--r--mk/linux.mk6
-rw-r--r--mk/macos.mk6
-rw-r--r--mk/windows.mk6
-rw-r--r--test-alert.h6
-rw-r--r--test-critical.h6
-rw-r--r--test-debug.h6
-rw-r--r--test-emergency.h6
-rw-r--r--test-error.h6
-rw-r--r--test-info.h6
-rw-r--r--test-level.c4
-rw-r--r--test-level_cork.c4
-rw-r--r--test-notice.h6
-rw-r--r--test-trace.h6
-rw-r--r--test-vlevel.c4
-rw-r--r--test-vlevel_cork.c4
-rw-r--r--test-warning.h6
-rw-r--r--testhelp.c366
99 files changed, 5808 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..42c07ce
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,16 @@
+*\#*
+*~
+*.o
+*.a
+*.lo
+*.su
+*.so
+*.so.*
+*.dll
+*.dylib
+*.gch
+*.gcov
+*.gcno
+*.gcda
+*.to
+*.test
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..f930ea6
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+© 2024 Mattias Andrée <m@maandree.se>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..cef8c52
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,182 @@
+.POSIX:
+
+CONFIGFILE = config.mk
+include $(CONFIGFILE)
+
+OS = linux
+# Linux: linux
+# Mac OS: macos
+# Windows: windows
+include mk/$(OS).mk
+
+
+LIB_MAJOR = 1
+LIB_MINOR = 0
+LIB_VERSION = $(LIB_MAJOR).$(LIB_MINOR)
+LIB_NAME = log
+
+
+OBJ =\
+ liblog_alert.o\
+ liblog_alert_cork.o\
+ liblog_apply_env_mask.o\
+ liblog_clear_mask.o\
+ liblog_critical.o\
+ liblog_critical_cork.o\
+ liblog_debug.o\
+ liblog_debug_cork.o\
+ liblog_destroy_context.o\
+ liblog_destroy_output.o\
+ liblog_dump_backtrace.o\
+ liblog_dump_backtrace_cork.o\
+ liblog_emergency.o\
+ liblog_emergency_cork.o\
+ liblog_error.o\
+ liblog_error_cork.o\
+ liblog_flush__.o\
+ liblog_info.o\
+ liblog_info_cork.o\
+ liblog_init_context.o\
+ liblog_log.o\
+ liblog_log_cork.o\
+ liblog_log_no_backtrace.o\
+ liblog_log_no_backtrace_cork.o\
+ liblog_logmask__.o\
+ liblog_mask_level.o\
+ liblog_mask_range.o\
+ liblog_mask_verbose.o\
+ liblog_notice.o\
+ liblog_notice_cork.o\
+ liblog_trace.o\
+ liblog_trace__.o\
+ liblog_trace_cork.o\
+ liblog_uncork.o\
+ liblog_unmask_level.o\
+ liblog_unmask_range.o\
+ liblog_unmask_verbose.o\
+ liblog_use_fd.o\
+ liblog_use_fd_for_range.o\
+ liblog_use_file.o\
+ liblog_use_file_for_range.o\
+ liblog_use_output.o\
+ liblog_use_stderr.o\
+ liblog_use_stderr_for_range.o\
+ liblog_use_stream.o\
+ liblog_use_stream_for_range.o\
+ liblog_use_syslog.o\
+ liblog_use_syslog_for_range.o\
+ liblog_valert.o\
+ liblog_valert_cork.o\
+ liblog_vcritical.o\
+ liblog_vcritical_cork.o\
+ liblog_vdebug.o\
+ liblog_vdebug_cork.o\
+ liblog_vemergency.o\
+ liblog_vemergency_cork.o\
+ liblog_verror.o\
+ liblog_verror_cork.o\
+ liblog_vinfo.o\
+ liblog_vinfo_cork.o\
+ liblog_vlog.o\
+ liblog_vlog_cork.o\
+ liblog_vlog_no_backtrace.o\
+ liblog_vlog_no_backtrace_cork.o\
+ liblog_vnotice.o\
+ liblog_vnotice_cork.o\
+ liblog_vtrace.o\
+ liblog_vtrace_cork.o\
+ liblog_vwarning.o\
+ liblog_vwarning_cork.o\
+ liblog_vxlog.o\
+ liblog_warning.o\
+ liblog_warning_cork.o\
+ liblog_whence__.o\
+ liblog_xlog.o
+
+HDR =\
+ liblog.h\
+ common.h
+
+LOBJ = $(OBJ:.o=.lo)
+TOBJ = $(OBJ:.o=.to)
+TEST = $(OBJ:.o=.test)
+
+
+all: liblog.a liblog.$(LIBEXT) $(TEST)
+$(OBJ): $(HDR)
+$(LOBJ): $(HDR)
+$(TOBJ): $(HDR)
+$(TEST): testhelp.o liblog.a
+liblog_alert_cork.to liblog_critical_cork.to liblog_debug_cork.to liblog_emergency_cork.to liblog_error_cork.to liblog_notice_cork.to liblog_trace_cork.to liblog_warning_cork.to: test-level_cork.c
+liblog_valert_cork.to liblog_vcritical_cork.to liblog_vdebug_cork.to liblog_vemergency_cork.to liblog_verror_cork.to liblog_vnotice_cork.to liblog_vtrace_cork.to liblog_vwarning_cork.to: test-vlevel_cork.c
+liblog_alert.to liblog_critical.to liblog_debug.to liblog_emergency.to liblog_error.to liblog_notice.to liblog_trace.to liblog_warning.to: test-level.c
+liblog_valert.to liblog_vcritical.to liblog_vdebug.to liblog_vemergency.to liblog_verror.to liblog_vnotice.to liblog_vtrace.to liblog_vwarning.to: test-vlevel.c
+liblog_alert_cork.to liblog_valert_cork.to liblog_alert.to liblog_valert.to: test-alert.h
+liblog_critical_cork.to liblog_vcritical_cork.to liblog_critical.to liblog_vcritical.to: test-critical.h
+liblog_debug_cork.to liblog_vdebug_cork.to liblog_debug.to liblog_vdebug.to: test-debug.h
+liblog_emergency_cork.to liblog_vemergency_cork.to liblog_emergency.to liblog_vemergency.to: test-emergency.h
+liblog_error_cork.to liblog_verror_cork.to liblog_error.to liblog_verror.to: test-error.h
+liblog_notice_cork.to liblog_vnotice_cork.to liblog_notice.to liblog_vnotice.to: test-notice.h
+liblog_trace_cork.to liblog_vtrace_cork.to liblog_trace.to liblog_vtrace.to: test-trace.h
+liblog_warning_cork.to liblog_vwarning_cork.to liblog_warning.to liblog_vwarning.to: test-warning.h
+
+.c.o:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.lo:
+ $(CC) -fPIC -c -o $@ $< $(CFLAGS) $(CPPFLAGS)
+
+.c.to:
+ $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) -DTEST
+
+.to.test:
+ $(CC) -o $@ $< testhelp.o liblog.a $(LDFLAGS)
+
+liblog.a: $(OBJ)
+ @rm -f -- $@
+ $(AR) rc $@ $(OBJ)
+ $(AR) ts $@ > /dev/null
+
+liblog.$(LIBEXT): $(LOBJ)
+ $(CC) $(LIBFLAGS) -o $@ $(LOBJ) $(LDFLAGS)
+
+check: $(TEST)
+ @set -e;\
+ status=0;\
+ for t in $(TEST); do\
+ if test -n "$(CHECK_PREFIX)"; then\
+ printf '%s\n' "$(CHECK_PREFIX) ./$$t";\
+ else\
+ printf '%s\n' "./$$t";\
+ fi;\
+ if ! $(CHECK_PREFIX) ./$$t; then\
+ status=1;\
+ fi;\
+ done;\
+ exit $$status
+
+install: liblog.a liblog.$(LIBEXT)
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/lib"
+ mkdir -p -- "$(DESTDIR)$(PREFIX)/include"
+ cp -- liblog.a "$(DESTDIR)$(PREFIX)/lib/"
+ cp -- liblog.$(LIBEXT) "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBMINOREXT)"
+ $(FIX_INSTALL_NAME) "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBMINOREXT)"
+ ln -sf -- liblog.$(LIBMINOREXT) "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBMAJOREXT)"
+ ln -sf -- liblog.$(LIBMAJOREXT) "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBEXT)"
+ cp -- liblog.h "$(DESTDIR)$(PREFIX)/include/"
+
+uninstall:
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblog.a"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBMAJOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBMINOREXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/lib/liblog.$(LIBEXT)"
+ -rm -f -- "$(DESTDIR)$(PREFIX)/include/liblog.h"
+
+clean:
+ -rm -f -- *.o *.a *.lo *.su *.so *.so.* *.dll *.dylib *.to *.test
+ -rm -f -- *.gch *.gcov *.gcno *.gcda *.$(LIBEXT)
+
+.SUFFIXES:
+.SUFFIXES: .lo .o .c .to .test
+
+.PHONY: all check install uninstall clean
diff --git a/TODO b/TODO
new file mode 100644
index 0000000..dc72593
--- /dev/null
+++ b/TODO
@@ -0,0 +1,2 @@
+Add man pages
+Add README
diff --git a/common.h b/common.h
new file mode 100644
index 0000000..277c3d5
--- /dev/null
+++ b/common.h
@@ -0,0 +1,316 @@
+/* See LICENSE file for copyright and license details. */
+#include "liblog.h"
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+#include <unistd.h>
+
+
+#define DEFAULT_PREFIXFMT "%[argv0] [%[pid]] [%{utime:%F %T%}.%[milli]Z] [%level]: %[file]:%[line] (%[function]): "
+
+
+#if defined(__GNUC__)
+# define WEAK_LINKING __attribute__((__weak__))
+#else
+# define WEAK_LINKING
+# define WEAK_LINKING_FAILED
+#endif
+
+
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+#define NEXT_LOGLEVEL(LVL) ((enum liblog_level)(LVL) + (enum liblog_level)100)
+
+#define XLOG_NOT_INLINE 0x0100
+
+
+struct messagebuf {
+ char *prefix; /* TODO for each output channel; also get arguments for each channel */
+ char *text;
+ size_t len;
+ size_t size;
+};
+
+#define MESSAGEBUF_INIT ((struct messagebuf){NULL, NULL, 0, 0})
+
+struct liblog_context_internal_state {
+ struct messagebuf msg;
+};
+
+
+extern const char *argv0;
+
+
+int liblog_flush__(struct liblog_context *ctx, const struct messagebuf *msg);
+WEAK_LINKING int liblog_trace__(char **textp, size_t *offsetp, size_t *allocsizep, size_t skip, void *saved_trace);
+WEAK_LINKING int liblog_whence__(char **file_out, off_t *line_out, char **function_out, size_t skip, void **trace_savep);
+
+
+#ifdef TEST
+# if defined(__GNUC__)
+# if defined(__clang__)
+# pragma clang diagnostic ignored "-Wdisabled-macro-expansion"
+# endif
+# define main __attribute__((__const__)) main
+# endif
+#endif
+
+
+
+
+#ifdef TEST
+# define TESTED_ELSEWHERE LIBEXEC_CONST__ int main(void) { return 0; }
+
+enum assert_type {
+ ASSERT_ENUM,
+ ASSERT_UINT,
+ ASSERT_INT,
+ ASSERT_NULL,
+ ASSERT_PTR,
+ ASSERT_STR,
+ ASSERT_STR_REV
+};
+
+enum assert_how {
+ ASSERT_EQ,
+ ASSERT_NE,
+ ASSERT_LT,
+ ASSERT_LE,
+ ASSERT_GT,
+ ASSERT_GE,
+ ASSERT_CONTAINS_ALL,
+ ASSERT_CONTAINS_ANY,
+ ASSERT_CONTAINS_NOT_ALL,
+ ASSERT_CONTAINS_NOT_ANY,
+ ASSERT_IS_IN,
+ ASSERT_NOT_IN,
+ ASSERT_NOT_LT,
+ ASSERT_NOT_LE,
+ ASSERT_NOT_GT,
+ ASSERT_NOT_GE
+};
+
+void test_assert(const char *file, int line, enum assert_type type, enum assert_how how,
+ const char *have_string, const char *expect_string, const void *have, const void *expect);
+
+#define ASSERT_EQ_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_NE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LT_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_LT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GT_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_GT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_LE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GE_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_GE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_ALL_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_SOME_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NONE_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_NOT_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NOT_ALL_OF_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_IS_IN_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_IS_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NOT_IN_ENUM(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_ENUM, ASSERT_NOT_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_EQ_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_NE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_NE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LT_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_LT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GT_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_GT, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_LE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_LE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_GE_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_GE, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_ALL_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_SOME_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NONE_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_NOT_ANY, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_CONTAINS_NOT_ALL_OF_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_IS_IN_UINT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_UINT, ASSERT_IS_IN, #HAVE, #EXPECT,\
+ &(uintmax_t){(uintmax_t)(HAVE)}, &(uintmax_t){(uintmax_t)(EXPECT)})
+
+#define ASSERT_EQ_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_EQ, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_NE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_NE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_LT_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_LT, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_GT_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_GT, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_LE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_LE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_GE_INT(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_INT, ASSERT_GE, #HAVE, #EXPECT,\
+ &(intmax_t){(intmax_t)(HAVE)}, &(intmax_t){(intmax_t)(EXPECT)})
+
+#define ASSERT_EQ_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_EQ, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NE_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_STARTS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_EXTENDS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_TRUNCATES_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEGINNING_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_EXTENDS_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_TRUNCATES_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDING_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_STARTS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_EXTENDS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_TRUNCATES_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEGINNING_NOT_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_ENDS_WITH_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_EXTENDS_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_TRUNCATES_END_OF_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_ENDING_NOT_BOUNDED_TO_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR_REV, ASSERT_NOT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_CONTAINS_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_CONTAINS_ALL, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_DOES_NOT_CONTAIN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_CONTAINS_NOT_ALL, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_IN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_IS_IN, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NOT_IN_STR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_STR, ASSERT_NOT_IN, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_EQ, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_NOT_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_NE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEFORE_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_LT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_AFTER_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_GT, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_BEFORE_OR_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_LE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_AFTER_OR_IS_PTR(HAVE, EXPECT)\
+ test_assert(__FILE__, __LINE__, ASSERT_PTR, ASSERT_GE, #HAVE, #EXPECT, (HAVE), (EXPECT))
+
+#define ASSERT_IS_NULL(HAVE)\
+ test_assert(__FILE__, __LINE__, ASSERT_NULL, ASSERT_EQ, #HAVE, "NULL", (HAVE), NULL)
+
+#define ASSERT_NOT_NULL(HAVE)\
+ test_assert(__FILE__, __LINE__, ASSERT_NULL, ASSERT_NE, #HAVE, "NULL", (HAVE), NULL)
+
+#define ASSERT_IS_TRUE(HAVE)\
+ ASSERT_NE_INT(HAVE, 0)
+
+#define ASSERT_IS_FALSE(HAVE)\
+ ASSERT_EQ_INT(HAVE, 0)
+
+#define ASSERT_ZERO(HAVE)\
+ ASSERT_EQ_INT(HAVE, 0)
+
+#endif
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..f4adf12
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,8 @@
+PREFIX = /usr
+MANPREFIX = $(PREFIX)/share/man
+
+CC = c99
+
+CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 -D_GNU_SOURCE
+CFLAGS =
+LDFLAGS =
diff --git a/liblog.h b/liblog.h
new file mode 100644
index 0000000..5e2fad1
--- /dev/null
+++ b/liblog.h
@@ -0,0 +1,1583 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef LIBLOG_H
+#define LIBLOG_H
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+
+
+
+#if defined(__clang__)
+# pragma clang diagnostic push
+# pragma clang diagnostic ignored "-Wformat-nonliteral"
+# pragma clang diagnostic ignored "-Wpadded"
+#elif defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+
+/**
+ * Mark the message as incomplete an deferred
+ * printing until LIBLOG_XLOG_UNCORK is used.
+ */
+#define LIBLOG_XLOG_CORK 0x0001
+
+/**
+ * Output message printed since the first
+ * LIBLOG_XLOG_CORK (after the last
+ * LIBLOG_XLOG_UNCORK if any) as one complete
+ * message.
+ */
+#define LIBLOG_XLOG_UNCORK 0x0002
+
+/**
+ * Output a backtrace even if the it is not
+ * configured that backtraces should be printed
+ * for the selected log level.
+ */
+#define LIBLOG_XLOG_BACKTRACE 0x0004
+
+/**
+ * Do not output a backtrace even if the it is
+ * configured that backtraces should be printed
+ * for the selected log level.
+ */
+#define LIBLOG_XLOG_NO_BACKTRACE 0x0008
+
+/**
+ * Do not output any new text (apport from potential
+ * backtrace); allowing `fmt` to be `NULL`.
+ */
+#define LIBLOG_XLOG_NO_TEXT 0x0010
+
+
+/**
+ * Opaque data type for the internal state
+ * of a `struct liblog_context`
+ */
+struct liblog_context_internal_state;
+
+/**
+ * Log levels
+ */
+enum liblog_level {
+ /**
+ * System is unusable
+ */
+ LIBLOG_EMERGENCY = 0,
+
+ /**
+ * Action must be taken immediately
+ */
+ LIBLOG_ALERT = 100,
+
+ /**
+ * Critical condition
+ */
+ LIBLOG_CRITICAL = 200,
+
+ /**
+ * Error condition
+ */
+ LIBLOG_ERROR = 300,
+
+ /**
+ * Warning condition
+ */
+ LIBLOG_WARNING = 400,
+
+ /**
+ * Normal, but significant, condition
+ */
+ LIBLOG_NOTICE = 500,
+
+ /**
+ * Informational message
+ */
+ LIBLOG_INFO = 600,
+
+ /**
+ * Less verbose debug-level message: what is happening on a high level
+ */
+ LIBLOG_TRACE = 700,
+
+ /**
+ * More verbose debug-level message: detailed information about what is going on
+ */
+ LIBLOG_DEBUG = 800
+};
+
+/**
+ * Log channel types
+ */
+enum liblog_sink {
+ /**
+ * Output to syslog
+ */
+ LIBLOG_SYSLOG,
+
+ /**
+ * Output to a file descriptor
+ */
+ LIBLOG_FILE,
+
+ /**
+ * Output to a `FILE`
+ */
+ LIBLOG_STREAM
+};
+
+/**
+ * Log output configurations; mapping of log levels to log channels
+ *
+ * Destroy with `liblog_destroy_output` if destroying manually
+ */
+struct liblog_output {
+ /**
+ * User defined data
+ *
+ * This could for example be a path name to the output
+ * file so that the application knows which file that
+ * should be subject to rotation when online log rotation
+ * is performed
+ */
+ void *userdata;
+
+ /**
+ * The prefix for each log line
+ *
+ * A string where the following special symbols will be replaced
+ * - %{ltime: %} span that marks formatting of the current local time with strftime(3)
+ * - %{utime: %} span that marks formatting of the current UTC time with strftime(3)
+ * - %[nano] the nanoseconds of the current time, printed with 9 digits
+ * - %[micro] the microseconds of the current time, printed with 6 digits
+ * - %[milli] the milliseconds of the current time, printed with 3 digits
+ * - %[centi] the centiseconds of the current time, printed with 2 digits
+ * - %[deci] the deciseconds of the current time, printed with 1 digit
+ * - %[function] function where the log message is being printed from
+ * - %[file] file where the log message is being printed from
+ * - %[line] line in file where the log message is being printed from
+ * - %[level] log level for the message rounded down to a predefined value
+ * (printed as string)
+ * - %[xlevel] the exact log level for the message, printed as the name of
+ * of the log level rounded down to a predfined value, followed
+ * by an adjustment
+ * - %[tid] the process's thread ID
+ * - %[pid] the process's thread group ID (process ID)
+ * - %[ppid] the process's parent process ID
+ * - %[pgid] the process's process group ID
+ * - %[sid] the process's session ID
+ * - %[uid] the real user ID of the process
+ * - %[euid] the effective user ID of the process
+ * - %[gid] the real group ID of the process
+ * - %[egid] the effective group ID of the process
+ * - %[name] the name of process
+ * - %[argv0] the value of the global `const char *argv0`
+ * - %% a literal %
+ *
+ * If `NULL`, this with be replace with a default format
+ */
+ const char *prefixfmt;
+
+ /**
+ * If set `.lowest_verbosity` is treated as set to
+ * the lowest possible value
+ */
+ unsigned lowest_verbosity_unlimited : 1;
+
+ /**
+ * If set `.higest_verbosity` is treated as set to
+ * the highest possible value
+ */
+ unsigned highest_verbosity_unlimited : 1;
+
+ /**
+ * If set, each log message will be prefixed with
+ * a backtrace
+ */
+ unsigned use_backtrace : 1;
+
+ unsigned : 0;
+
+ /**
+ * The lowest log level, in regards to verbosity
+ * but higest in regard to criticality, for which
+ * this log output channel shall be used
+ *
+ * Must not be greater than `.highest_verbosity`
+ */
+ enum liblog_level lowest_verbosity;
+
+ /**
+ * The higest log level, in regards to verbosity
+ * but lowest in regard to criticality, for which
+ * this log output channel shall be used
+ *
+ * Must not be less than `.lowest_verbosity`
+ */
+ enum liblog_level highest_verbosity;
+
+ /**
+ * The output channel type
+ */
+ enum liblog_sink sink_type;
+
+ /**
+ * The output channel
+ */
+ union {
+ /**
+ * Used when `.sink_type` is `LIBLOG_SYSLOG`
+ *
+ * The application is responsible for opening and closing syslog
+ */
+ struct {
+ /**
+ * The syslog log level to use
+ */
+ int level;
+ } syslog;
+
+ /**
+ * Used when `.sink_type` is `LIBLOG_FILE`
+ */
+ struct {
+ /**
+ * The file descriptor to write to
+ */
+ int fd;
+
+ /**
+ * Whether the library shall close the file descriptor
+ * when destroying the `struct liblog_output` object
+ */
+ unsigned owns_fd : 1;
+ } file;
+
+ /**
+ * Used when `.sink_type` is `LIBLOG_STREAM`
+ */
+ struct {
+ /**
+ * The stream to write to
+ */
+ FILE *stream;
+
+ /**
+ * Whether the library shall close the stream
+ * when destroying the `struct liblog_output` object
+ */
+ unsigned owns_stream : 1;
+ } stream;
+ } sink;
+};
+
+/**
+ * Logging configurations and state
+ *
+ * Initialise with `liblog_init_context`, and then
+ * configure output using any of the follow functions:
+ * - liblog_use_fd
+ * - liblog_use_fd_for_range
+ * - liblog_use_file
+ * - liblog_use_file_for_range
+ * - liblog_use_stderr
+ * - liblog_use_stderr_for_range
+ * - liblog_use_stream
+ * - liblog_use_stream_for_range
+ * - liblog_use_syslog
+ * - liblog_use_syslog_for_range
+ * - liblog_use_output
+ *
+ * Destroy with `liblog_destroy_context`
+ */
+struct liblog_context {
+ /**
+ * Opaque state
+ */
+ struct liblog_context_internal_state *internal_state;
+
+ /**
+ * Log output configurations; mapping of log levels to log channels
+ */
+ struct liblog_output *outputs;
+
+ /**
+ * Number of elements in noutputs
+ */
+ size_t noutputs;
+
+ /**
+ * Mask of log level groups to exclude when logging
+ */
+ unsigned logmask;
+};
+/* TODO add multithreading support */
+
+
+
+/* for internal use { */
+#define LIBLOG_VA__(FUNC, ...)\
+ int ret;\
+ va_list args;\
+ va_start(args, fmt);\
+ ret = FUNC(__VA_ARGS__, args);\
+ va_end(args);\
+ return ret
+
+#define LIBLOG_OUTPUT_UNRANGED__(...)\
+ (&(struct liblog_output){\
+ .userdata = NULL,\
+ .prefixfmt = prefixfmt,\
+ .lowest_verbosity_unlimited = 1U,\
+ .highest_verbosity_unlimited = 1U,\
+ .use_backtrace = (use_backtrace ? 1U : 0U),\
+ __VA_ARGS__\
+ })
+
+#define LIBLOG_OUTPUT_RANGED__(...)\
+ (&(struct liblog_output){\
+ .userdata = NULL,\
+ .prefixfmt = prefixfmt,\
+ .lowest_verbosity_unlimited = 0U,\
+ .highest_verbosity_unlimited = 0U,\
+ .use_backtrace = (use_backtrace ? 1U : 0U),\
+ .lowest_verbosity = least_verbose,\
+ .highest_verbosity = most_verbose,\
+ __VA_ARGS__\
+ })
+
+#if defined(__clang__)
+# define LIBLOG_PRINTF__(FMTIDX) __attribute__((__format__(__printf__, (FMTIDX), (FMTIDX) + 1)))
+# define LIBLOG_VPRINTF__(FMTIDX) __attribute__((__format__(__printf__, (FMTIDX), 0)))
+#elif defined(__GNUC__)
+# define LIBLOG_PRINTF__(FMTIDX) __attribute__((__format__(__gnu_printf__, (FMTIDX), (FMTIDX) + 1)))
+# define LIBLOG_VPRINTF__(FMTIDX) __attribute__((__format__(__gnu_printf__, (FMTIDX), 0)))
+#else
+# define LIBLOG_PRINTF__(FMTIDX)
+# define LIBLOG_VPRINTF__(FMTIDX)
+#endif
+
+#if defined(__GNUC__)
+# define LIBLOG_CONST__ __attribute__((__const__))
+#else
+# define LIBLOG_CONST__
+#endif
+
+LIBLOG_CONST__ unsigned liblog_logmask__(enum liblog_level least_verbose, enum liblog_level most_verbose);
+/* } */
+
+
+
+/**
+ * Initialise a `struct liblog_context`
+ *
+ * @param ctx That object to initialise
+ * @return 0 on success, -1 on failure
+ */
+int liblog_init_context(struct liblog_context *ctx);
+
+/**
+ * Destroy a `struct liblog_context`
+ *
+ * @param ctx That object to destroy
+ * @return 0 on success, -1 on failure (object will
+ * still be completely destroyed)
+ */
+int liblog_destroy_context(struct liblog_context *ctx);
+
+/**
+ * Destroy a `struct liblog_output`
+ *
+ * @param output That object to destroy
+ * @return 0 on success, -1 on failure (object will
+ * still be completely destroyed)
+ */
+int liblog_destroy_output(struct liblog_output *output);
+
+
+/**
+ * Remove log level mask, causing all log messages, that
+ * have an output configured, to be printed
+ *
+ * @param ctx Logging configurations and state
+ */
+inline void
+liblog_clear_mask(struct liblog_context *ctx)
+{ if (ctx) ctx->logmask = 0U; }
+
+/**
+ * Apply log level mask that is configured in the
+ * process's environment variable "LIBLOG_LOGMASK"
+ *
+ * This is done automatically by `liblog_init_context`
+ *
+ * If the LIBLOG_LOGMASK environment variable is
+ * unset or empty, anything more verbose than a
+ * some specific level (currently `LIBLOG_WARNING`)
+ * will be filtered.
+ *
+ * @param ctx Logging configurations and state
+ */
+void liblog_apply_env_mask(struct liblog_context *ctx);
+
+/**
+ * Stop log messages within a certain range of
+ * log levels from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param least_verbose The lowest (least verbose, most critical) log level
+ * to filter out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ * @param most_verbose The highest (most verbose, least critical) log level
+ * to filter out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ *
+ * This function has no effect if, after rounding, `least_verbose > most_verbose`
+ */
+inline void
+liblog_mask_range(struct liblog_context *ctx, enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ if (ctx) ctx->logmask |= liblog_logmask__(least_verbose, most_verbose); }
+
+/**
+ * Stop log messages with a certain log level or
+ * higher (more verbose, less critical) from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param least The lowest (least verbose, most critical) log level to
+ * filter out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ */
+inline void
+liblog_mask_verbose(struct liblog_context *ctx, enum liblog_level least)
+{ liblog_mask_range(ctx, least, LIBLOG_DEBUG); }
+
+/**
+ * Stop log messages with a certain log level group
+ * (all levels up to but excluding the next predefined
+ * log level) from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param group The log level to filter out; will be rounded
+ * up or down (unspecified which) to the closest
+ * predefined log level value
+ */
+inline void
+liblog_mask_level(struct liblog_context *ctx, enum liblog_level group)
+{ liblog_mask_range(ctx, group, group); }
+
+/**
+ * Stop filtering out log messages within a certain
+ * range of log levels from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param least_verbose The lowest (least verbose, most critical) log level to
+ * stop filtering out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ * @param most_verbose The highest (most verbose, least critical) log level to
+ * filtering out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ *
+ * This function has no effect if, after rounding, `least_verbose > most_verbose`
+ */
+inline void
+liblog_unmask_range(struct liblog_context *ctx, enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ if (ctx) ctx->logmask &= ~liblog_logmask__(least_verbose, most_verbose); }
+
+/**
+ * Stop filtering out log messages with a certain log level or
+ * higher (more verbose, less critical) from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param least The lowest (least verbose, most critical) log level to
+ * stop filtering out; will be rounded up or down (unspecified
+ * which) to the closest predefined log level value
+ */
+inline void
+liblog_unmask_verbose(struct liblog_context *ctx, enum liblog_level least)
+{ liblog_unmask_range(ctx, least, LIBLOG_DEBUG); }
+
+/**
+ * Stop filtering out log messages with a certain log level
+ * group (all levels up to but excluding the next predefined
+ * log level) from being printed
+ *
+ * @param ctx Logging configurations and state
+ * @param group The log level to stop filtering out; will be
+ * rounded up or down (unspecified which) to the
+ * closest predefined log level value
+ */
+inline void
+liblog_unmask_level(struct liblog_context *ctx, enum liblog_level group)
+{ liblog_unmask_range(ctx, group, group); }
+
+
+/**
+ * Add a log output
+ *
+ * @param ctx Logging configurations and state
+ * @param output The configurations for the additional output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+struct liblog_output *liblog_use_output(struct liblog_context *ctx, const struct liblog_output *output);
+
+/**
+ * Set up logging with syslog(3)
+ *
+ * @param ctx Logging configurations and state
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @return 0 on success, -1 on failure
+ *
+ * This function adds multi new output configurations to `ctx`
+ */
+int liblog_use_syslog(struct liblog_context *ctx, const char *prefixfmt, int use_backtrace);
+
+/**
+ * Set up logging with syslog(3)
+ *
+ * @param ctx Logging configurations and state
+ * @param syslog_level The syslog(3) log level that shall be used
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @param least_verbose Lower (in regard to verbosity, upper in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @param most_verbose Upper (in regard to verbosity, lower in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_syslog_for_range(struct liblog_context *ctx, int syslog_level, const char *prefixfmt, int use_backtrace,
+ enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ return liblog_use_output(ctx, LIBLOG_OUTPUT_RANGED__(.sink_type = LIBLOG_SYSLOG, .sink.syslog = {syslog_level})); }
+
+/**
+ * Set up logging to a file descriptor
+ *
+ * @param ctx Logging configurations and state
+ * @param fd The file descriptor to log to
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_fd(struct liblog_context *ctx, int fd, const char *prefixfmt, int use_backtrace)
+{ return liblog_use_output(ctx, LIBLOG_OUTPUT_UNRANGED__(.sink_type = LIBLOG_FILE, .sink.file = {fd, 0})); }
+
+/**
+ * Set up logging to a file descriptor
+ *
+ * @param ctx Logging configurations and state
+ * @param fd The file descriptor to log to
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @param least_verbose Lower (in regard to verbosity, upper in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @param most_verbose Upper (in regard to verbosity, lower in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_fd_for_range(struct liblog_context *ctx, int fd, const char *prefixfmt, int use_backtrace,
+ enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ return liblog_use_output(ctx, LIBLOG_OUTPUT_RANGED__(.sink_type = LIBLOG_FILE, .sink.file = {fd, 0})); }
+
+/**
+ * Set up logging to an output stream
+ *
+ * @param ctx Logging configurations and state
+ * @param stream The stream to log to
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_stream(struct liblog_context *ctx, FILE *stream, const char *prefixfmt, int use_backtrace)
+{ return liblog_use_output(ctx, LIBLOG_OUTPUT_UNRANGED__(.sink_type = LIBLOG_STREAM, .sink.stream = {stream, 0})); }
+
+/**
+ * Set up logging to an output stream
+ *
+ * @param ctx Logging configurations and state
+ * @param stream The stream to log to
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @param least_verbose Lower (in regard to verbosity, upper in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @param most_verbose Upper (in regard to verbosity, lower in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_stream_for_range(struct liblog_context *ctx, FILE *stream, const char *prefixfmt, int use_backtrace,
+ enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ return liblog_use_output(ctx, LIBLOG_OUTPUT_RANGED__(.sink_type = LIBLOG_STREAM, .sink.stream = {stream, 0})); }
+
+/**
+ * Set up logging to a file
+ *
+ * @param ctx Logging configurations and state
+ * @param dirfd File descriptor to the directory `path` is relative to if
+ * it is a relative path, or -1 to for the path to be absolute,
+ * failing with EINVAL if it is relative); AT_FDCWD for the
+ * current working directory
+ * @param path The path of the file to write to, if `NULL` or `""`
+ * dirfd will be used as the file descriptor to write to;
+ * if the file exists, it is opened in append mode, otherwise
+ * it is created to readable by every user but only writable
+ * by the current user
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+struct liblog_output *liblog_use_file(struct liblog_context *ctx, int dirfd, const char *path,
+ const char *prefixfmt, int use_backtrace);
+
+/**
+ * Set up logging to a file
+ *
+ * @param ctx Logging configurations and state
+ * @param dirfd File descriptor to the directory `path` is relative to if
+ * it is a relative path, or -1 to for the path to be absolute,
+ * failing with EINVAL if it is relative); AT_FDCWD for the
+ * current working directory
+ * @param path The path of the file to write to, if `NULL` or `""`
+ * dirfd will be used as the file descriptor to write to;
+ * if the file exists, it is opened in append mode, otherwise
+ * it is created to readable by every user but only writable
+ * by the current user
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @param least_verbose Lower (in regard to verbosity, upper in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @param most_verbose Upper (in regard to verbosity, lower in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+struct liblog_output *liblog_use_file_for_range(struct liblog_context *ctx, int dirfd, const char *path,
+ const char *prefixfmt, int use_backtrace,
+ enum liblog_level least_verbose, enum liblog_level most_verbose);
+
+/**
+ * Set up logging to the standard output
+ *
+ * @param ctx Logging configurations and state
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_stderr(struct liblog_context *ctx, const char *prefixfmt, int use_backtrace)
+{ return liblog_use_fd(ctx, 2, prefixfmt, use_backtrace); }
+
+/**
+ * Set up logging to the standard output
+ *
+ * @param ctx Logging configurations and state
+ * @param prefixfmt See `.prefixfmt` in `struct liblog_output`
+ * @param use_backtrace Whether each log message should be prefix with a log backtrace
+ * @param least_verbose Lower (in regard to verbosity, upper in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @param most_verbose Upper (in regard to verbosity, lower in regards to criticality),
+ * inclusive bound for the range liblog log level for which log
+ * messages should be output to the new output
+ * @return The new output configuration (this is an object
+ * owned by `ctx` and should not be deallocated,
+ * it is returned so that changes can be made)
+ * on success, `NULL` on failure
+ */
+inline struct liblog_output *
+liblog_use_stderr_for_range(struct liblog_context *ctx, const char *prefixfmt, int use_backtrace,
+ enum liblog_level least_verbose, enum liblog_level most_verbose)
+{ return liblog_use_fd_for_range(ctx, 2, prefixfmt, use_backtrace, least_verbose, most_verbose); }
+
+
+/**
+ * Write a log message with a custom log level
+ *
+ * This function's behaviour is customisable
+ * using its `flags` parameter
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param flags Modifications the the function's behaviour;
+ * may be 0 or the OR of any of the following values:
+ * - LIBLOG_XLOG_CORK:
+ * Mark the message as incomplete an deferred
+ * printing until LIBLOG_XLOG_UNCORK is used.
+ * - LIBLOG_XLOG_UNCORK:
+ * Output message printed since the first
+ * LIBLOG_XLOG_CORK (after the last
+ * LIBLOG_XLOG_UNCORK if any) as one complete
+ * message.
+ * - LIBLOG_XLOG_BACKTRACE:
+ * Output a backtrace even if the it is not
+ * configured that backtraces should be printed
+ * for the selected log level.
+ * - LIBLOG_XLOG_NO_BACKTRACE:
+ * Do not output a backtrace even if the it is
+ * configured that backtraces should be printed
+ * for the selected log level.
+ * - LIBLOG_XLOG_NO_TEXT:
+ * Do not output any new text (apport from potential
+ * backtrace); allowing `fmt` to be `NULL`.
+ * However, LIBLOG_XLOG_CORK and LIBLOG_XLOG_UNCORK
+ * cannot be combined, nor can LIBLOG_XLOG_BACKTRACE
+ * and LIBLOG_XLOG_NO_BACKTRACE be combined
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(4) int
+liblog_vxlog(struct liblog_context *ctx, enum liblog_level level, unsigned flags, const char *fmt, va_list args);
+
+/**
+ * Write a log message with a custom log level
+ *
+ * This function's behaviour is customisable
+ * using its `flags` parameter
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param flags Modifications the the function's behaviour;
+ * may be 0 or the OR of any of the following values:
+ * - LIBLOG_XLOG_CORK:
+ * Mark the message as incomplete an deferred
+ * printing until LIBLOG_XLOG_UNCORK is used
+ * - LIBLOG_XLOG_UNCORK:
+ * Output message printed since the first
+ * LIBLOG_XLOG_CORK (after the last
+ * LIBLOG_XLOG_UNCORK if any) as one complete
+ * message
+ * - LIBLOG_XLOG_BACKTRACE:
+ * Output a backtrace even if the it is not
+ * configured that backtraces should be printed
+ * for the selected log level
+ * - LIBLOG_XLOG_NO_BACKTRACE:
+ * Do not output a backtrace even if the it is
+ * configured that backtraces should be printed
+ * for the selected log level
+ * - LIBLOG_XLOG_NO_TEXT:
+ * Do not output any new text (apport from potential
+ * backtrace); allowing `fmt` to be `NULL`.
+ * However, LIBLOG_XLOG_CORK and LIBLOG_XLOG_UNCORK
+ * cannot be combined, nor can LIBLOG_XLOG_BACKTRACE
+ * and LIBLOG_XLOG_NO_BACKTRACE be combined
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(4) inline int
+liblog_xlog(struct liblog_context *ctx, enum liblog_level level, unsigned flags, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, flags, fmt); }
+
+
+/**
+ * If there is an incomplete log message being constructed,
+ * terminate it and print it
+ *
+ * @param ctx Logging configurations and state
+ * @return 0 on success, -1 on failure
+ *
+ * @seealso liblog_xlog
+ * @seealso liblog_dump_backtrace_cork
+ * @seealso liblog_log_cork
+ * @seealso liblog_log_no_backtrace_cork
+ * @seealso liblog_emergency_cork
+ * @seealso liblog_alert_cork
+ * @seealso liblog_critical_cork
+ * @seealso liblog_error_cork
+ * @seealso liblog_warning_cork
+ * @seealso liblog_notice_cork
+ * @seealso liblog_info_cork
+ * @seealso liblog_trace_cork
+ * @seealso liblog_debug_cork
+ */
+inline int
+liblog_uncork(struct liblog_context *ctx)
+{ return liblog_xlog(ctx, 0, LIBLOG_XLOG_NO_BACKTRACE | LIBLOG_XLOG_NO_TEXT | LIBLOG_XLOG_UNCORK, NULL); }
+
+
+/**
+ * Write the backtrace as a log message with a custom log level
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @return 0 on success, -1 on failure
+ */
+inline int
+liblog_dump_backtrace(struct liblog_context *ctx, enum liblog_level level)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_TEXT, NULL); }
+
+/**
+ * Write the backtrace as a log message with a custom log level
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * The backtrace will be terminated by a new line
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @return 0 on success, -1 on failure
+ */
+inline int
+liblog_dump_backtrace_cork(struct liblog_context *ctx, enum liblog_level level)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_TEXT | LIBLOG_XLOG_CORK, NULL); }
+
+
+/**
+ * Write a log message with a custom log level
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(3) inline int
+liblog_vlog(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, 0, fmt, args); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(3) inline int
+liblog_log(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vlog, ctx, level, fmt); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(3) inline int
+liblog_vlog_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_CORK, fmt, args); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(3) inline int
+liblog_log_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vlog_cork, ctx, level, fmt); }
+
+
+/**
+ * Write a log message with a custom log level
+ *
+ * Even if configured that backtraces should be printed for the
+ * the specificed log level, no backtrace will be printed
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(3) inline int
+liblog_vlog_no_backtrace(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_NO_BACKTRACE, fmt, args); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * Even if configured that backtraces should be printed for the
+ * the specificed log level, no backtrace will be printed
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(3) inline int
+liblog_log_no_backtrace(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vlog_no_backtrace, ctx, level, fmt); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * Even if configured that backtraces should be printed for the
+ * the specificed log level, no backtrace will be printed
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(3) inline int
+liblog_vlog_no_backtrace_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_NO_BACKTRACE | LIBLOG_XLOG_CORK, fmt, args); }
+
+/**
+ * Write a log message with a custom log level
+ *
+ * Even if configured that backtraces should be printed for the
+ * the specificed log level, no backtrace will be printed
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param level The log level for the message; does not have to
+ * be predefined in `enum liblog_level`
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(3) inline int
+liblog_log_no_backtrace_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vlog_no_backtrace_cork, ctx, level, fmt); }
+
+
+/* Everything from this point onwards is just for convenience */
+
+
+/**
+ * Write a log message with log level `LIBLOG_EMERGENCY`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vemergency(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_EMERGENCY, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_EMERGENCY`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_emergency(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vemergency, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_EMERGENCY`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vemergency_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_EMERGENCY, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_EMERGENCY`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_emergency_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vemergency_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_ALERT`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_valert(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_ALERT, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_ALERT`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_alert(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_valert, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_ALERT`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_valert_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_ALERT, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_ALERT`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_alert_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_valert_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_CRITICAL`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vcritical(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_CRITICAL, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_CRITICAL`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_critical(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vcritical, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_CRITICAL`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vcritical_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_CRITICAL, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_CRITICAL`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_critical_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vcritical_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_ERROR`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_verror(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_ERROR, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_ERROR`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_error(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_verror, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_ERROR`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_verror_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_ERROR, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_ERROR`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_error_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_verror_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_WARNING`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vwarning(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_WARNING, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_WARNING`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_warning(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vwarning, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_WARNING`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vwarning_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_WARNING, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_WARNING`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_warning_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vwarning_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_NOTICE`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vnotice(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_NOTICE, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_NOTICE`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_notice(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vnotice, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_NOTICE`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vnotice_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_NOTICE, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_NOTICE`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_notice_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vnotice_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_INFO`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vinfo(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_INFO, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_INFO`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_info(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vinfo, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_INFO`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vinfo_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_INFO, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_INFO`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_info_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vinfo_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_TRACE`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vtrace(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_TRACE, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_TRACE`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_trace(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vtrace, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_TRACE`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vtrace_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_TRACE, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_TRACE`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_trace_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vtrace_cork, ctx, fmt); }
+
+
+/**
+ * Write a log message with log level `LIBLOG_DEBUG`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vdebug(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog(ctx, LIBLOG_DEBUG, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_DEBUG`
+ *
+ * The log message is printed immediately as one complete log message
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_debug(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vdebug, ctx, fmt); }
+
+/**
+ * Write a log message with log level `LIBLOG_DEBUG`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param args Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_VPRINTF__(2) inline int
+liblog_vdebug_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vlog_cork(ctx, LIBLOG_DEBUG, fmt, args); }
+
+/**
+ * Write a log message with log level `LIBLOG_DEBUG`
+ *
+ * The log message is treated as incomplete, and can be extended using
+ * any log message writing function using the same `ctx`, and will be
+ * printed once `liblog_uncork` is called using the same `ctx`
+ *
+ * @param ctx Logging configurations and state
+ * @param fmt printf(3) format string for the log message
+ * @param ... Arguments for `fmt`
+ * @return 0 on success, -1 on failure
+ */
+LIBLOG_PRINTF__(2) inline int
+liblog_debug_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vdebug_cork, ctx, fmt); }
+
+
+
+#if defined(__clang__)
+# pragma clang diagnostic pop
+#elif defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+#endif
diff --git a/liblog_alert.c b/liblog_alert.c
new file mode 100644
index 0000000..ef0412c
--- /dev/null
+++ b/liblog_alert.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_alert(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_alert liblog__dont_want__
+# include "common.h"
+# undef liblog_alert
+
+LIBLOG_PRINTF__(2) int
+liblog_alert(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_ALERT, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-alert.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_alert_cork.c b/liblog_alert_cork.c
new file mode 100644
index 0000000..4583d9b
--- /dev/null
+++ b/liblog_alert_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_alert_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_alert_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_alert_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_alert_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_ALERT, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-alert.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_apply_env_mask.c b/liblog_apply_env_mask.c
new file mode 100644
index 0000000..1fceae0
--- /dev/null
+++ b/liblog_apply_env_mask.c
@@ -0,0 +1,22 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+void
+liblog_apply_env_mask(struct liblog_context *ctx)
+{
+ const char *env = getenv("LIBLOG_LOGMASK");
+
+ if (*env || !*env) {
+ liblog_mask_verbose(ctx, NEXT_LOGLEVEL(LIBLOG_WARNING));
+ return;
+ }
+
+ /* TODO implement and document format */
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_clear_mask.c b/liblog_clear_mask.c
new file mode 100644
index 0000000..4bd27ce
--- /dev/null
+++ b/liblog_clear_mask.c
@@ -0,0 +1,29 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_clear_mask(struct liblog_context *);
+
+#else
+
+int
+main(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_clear_mask(&ctx);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 1U;
+ liblog_clear_mask(&ctx);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_clear_mask(&ctx);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ return 0;
+}
+
+#endif
diff --git a/liblog_critical.c b/liblog_critical.c
new file mode 100644
index 0000000..c35dee9
--- /dev/null
+++ b/liblog_critical.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_critical(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_critical liblog__dont_want__
+# include "common.h"
+# undef liblog_critical
+
+LIBLOG_PRINTF__(2) int
+liblog_critical(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_CRITICAL, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-critical.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_critical_cork.c b/liblog_critical_cork.c
new file mode 100644
index 0000000..61547df
--- /dev/null
+++ b/liblog_critical_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_critical_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_critical_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_critical_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_critical_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_CRITICAL, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-critical.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_debug.c b/liblog_debug.c
new file mode 100644
index 0000000..b91b4bb
--- /dev/null
+++ b/liblog_debug.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_debug(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_debug liblog__dont_want__
+# include "common.h"
+# undef liblog_debug
+
+LIBLOG_PRINTF__(2) int
+liblog_debug(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_DEBUG, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-debug.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_debug_cork.c b/liblog_debug_cork.c
new file mode 100644
index 0000000..adac9a9
--- /dev/null
+++ b/liblog_debug_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_debug_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_debug_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_debug_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_debug_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_DEBUG, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-debug.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_destroy_context.c b/liblog_destroy_context.c
new file mode 100644
index 0000000..57192ac
--- /dev/null
+++ b/liblog_destroy_context.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+int
+liblog_destroy_context(struct liblog_context *ctx)
+{
+ int ret = 0;
+
+ if (!ctx)
+ return 0;
+
+ while (ctx->noutputs)
+ ret |= liblog_destroy_output(&ctx->outputs[--ctx->noutputs]);
+ free(ctx->outputs);
+ ctx->outputs = NULL;
+
+ if (ctx->internal_state) {
+ free(ctx->internal_state->msg.prefix);
+ free(ctx->internal_state->msg.text);
+ free(ctx->internal_state);
+ ctx->internal_state = NULL;
+ }
+
+ return ret;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_destroy_output.c b/liblog_destroy_output.c
new file mode 100644
index 0000000..98bb2dd
--- /dev/null
+++ b/liblog_destroy_output.c
@@ -0,0 +1,40 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+int
+liblog_destroy_output(struct liblog_output *output)
+{
+ int saved_errno = 0; /* initialised to silence false warning */
+ int ret = 0;
+
+ if (!output)
+ return 0;
+
+ if (output->sink_type == LIBLOG_FILE && output->sink.file.owns_fd) {
+ saved_errno = errno;
+ ret = close(output->sink.file.fd);
+ output->sink.file.fd = -1;
+ } else if (output->sink_type == LIBLOG_STREAM && output->sink.stream.owns_stream) {
+ saved_errno = errno;
+ ret = fclose(output->sink.stream.stream);
+ output->sink.stream.stream = NULL;
+#if EOF != -1
+ if (ret == EOF)
+ ret = -1;
+#endif
+ }
+
+ if (ret && errno != EINTR) {
+ ret = 0;
+ errno = saved_errno;
+ }
+
+ return ret;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_dump_backtrace.c b/liblog_dump_backtrace.c
new file mode 100644
index 0000000..54af846
--- /dev/null
+++ b/liblog_dump_backtrace.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_dump_backtrace(struct liblog_context *, enum liblog_level);
+
+# else
+# define liblog_dump_backtrace liblog__dont_want__
+# include "common.h"
+# undef liblog_dump_backtrace
+
+int
+liblog_dump_backtrace(struct liblog_context *ctx, enum liblog_level level)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_TEXT | XLOG_NOT_INLINE, NULL); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_dump_backtrace_cork.c b/liblog_dump_backtrace_cork.c
new file mode 100644
index 0000000..b44b4f1
--- /dev/null
+++ b/liblog_dump_backtrace_cork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_dump_backtrace_cork(struct liblog_context *, enum liblog_level);
+
+# else
+# define liblog_dump_backtrace_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_dump_backtrace_cork
+
+int
+liblog_dump_backtrace_cork(struct liblog_context *ctx, enum liblog_level level)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_TEXT | LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, NULL); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_emergency.c b/liblog_emergency.c
new file mode 100644
index 0000000..1ae80fa
--- /dev/null
+++ b/liblog_emergency.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_emergency(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_emergency liblog__dont_want__
+# include "common.h"
+# undef liblog_emergency
+
+LIBLOG_PRINTF__(2) int
+liblog_emergency(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_EMERGENCY, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-emergency.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_emergency_cork.c b/liblog_emergency_cork.c
new file mode 100644
index 0000000..34f36d7
--- /dev/null
+++ b/liblog_emergency_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_emergency_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_emergency_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_emergency_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_emergency_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_EMERGENCY, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-emergency.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_error.c b/liblog_error.c
new file mode 100644
index 0000000..e1bc858
--- /dev/null
+++ b/liblog_error.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_error(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_error liblog__dont_want__
+# include "common.h"
+# undef liblog_error
+
+LIBLOG_PRINTF__(2) int
+liblog_error(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_ERROR, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-error.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_error_cork.c b/liblog_error_cork.c
new file mode 100644
index 0000000..9ffa96c
--- /dev/null
+++ b/liblog_error_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_error_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_error_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_error_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_error_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_ERROR, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-error.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_flush__.c b/liblog_flush__.c
new file mode 100644
index 0000000..a0a1cb2
--- /dev/null
+++ b/liblog_flush__.c
@@ -0,0 +1,18 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+__attribute__((__const__))
+int
+liblog_flush__(struct liblog_context *ctx, const struct messagebuf *msg) /* TODO impl */
+{
+ (void) ctx;
+ (void) msg;
+ return 0;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_info.c b/liblog_info.c
new file mode 100644
index 0000000..9bf301f
--- /dev/null
+++ b/liblog_info.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_info(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_info liblog__dont_want__
+# include "common.h"
+# undef liblog_info
+
+LIBLOG_PRINTF__(2) int
+liblog_info(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_INFO, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-info.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_info_cork.c b/liblog_info_cork.c
new file mode 100644
index 0000000..83f8969
--- /dev/null
+++ b/liblog_info_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_info_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_info_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_info_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_info_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_INFO, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-info.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_init_context.c b/liblog_init_context.c
new file mode 100644
index 0000000..0137248
--- /dev/null
+++ b/liblog_init_context.c
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+int
+liblog_init_context(struct liblog_context *ctx)
+{
+ if (!ctx) {
+ errno = EINVAL;
+ return -1;
+ }
+ ctx->internal_state = NULL;
+ ctx->outputs = NULL;
+ ctx->noutputs = 0;
+ ctx->logmask = 0;
+ liblog_apply_env_mask(ctx);
+ return 0;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_log.c b/liblog_log.c
new file mode 100644
index 0000000..5601736
--- /dev/null
+++ b/liblog_log.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_log(struct liblog_context *, enum liblog_level, const char *, ...);
+
+# else
+# define liblog_log liblog__dont_want__
+# include "common.h"
+# undef liblog_log
+
+LIBLOG_PRINTF__(3) int
+liblog_log(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_log_cork.c b/liblog_log_cork.c
new file mode 100644
index 0000000..09f8f73
--- /dev/null
+++ b/liblog_log_cork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_log_cork(struct liblog_context *, enum liblog_level, const char *, ...);
+
+# else
+# define liblog_log_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_log_cork
+
+LIBLOG_PRINTF__(3) int
+liblog_log_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_log_no_backtrace.c b/liblog_log_no_backtrace.c
new file mode 100644
index 0000000..0767a3f
--- /dev/null
+++ b/liblog_log_no_backtrace.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_log_no_backtrace(struct liblog_context *, enum liblog_level, const char *, ...);
+
+# else
+# define liblog_log_no_backtrace liblog__dont_want__
+# include "common.h"
+# undef liblog_log_no_backtrace
+
+LIBLOG_PRINTF__(3) int
+liblog_log_no_backtrace(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, LIBLOG_XLOG_NO_BACKTRACE | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_log_no_backtrace_cork.c b/liblog_log_no_backtrace_cork.c
new file mode 100644
index 0000000..25e5223
--- /dev/null
+++ b/liblog_log_no_backtrace_cork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_log_no_backtrace_cork(struct liblog_context *, enum liblog_level, const char *, ...);
+
+# else
+# define liblog_log_no_backtrace_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_log_no_backtrace_cork
+
+LIBLOG_PRINTF__(3) int
+liblog_log_no_backtrace_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, LIBLOG_XLOG_NO_BACKTRACE | LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_logmask__.c b/liblog_logmask__.c
new file mode 100644
index 0000000..2a55850
--- /dev/null
+++ b/liblog_logmask__.c
@@ -0,0 +1,198 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+unsigned
+liblog_logmask__(enum liblog_level least_verbose, enum liblog_level most_verbose)
+{
+ unsigned min, max;
+
+ least_verbose = MAX(least_verbose, 0);
+ least_verbose = MIN(least_verbose, LIBLOG_DEBUG);
+ most_verbose = MAX(most_verbose, 0);
+ most_verbose = MIN(most_verbose, LIBLOG_DEBUG);
+
+ min = (unsigned)least_verbose / (unsigned)NEXT_LOGLEVEL(0);
+ max = (unsigned)most_verbose / (unsigned)NEXT_LOGLEVEL(0);
+
+ if (min > max)
+ return 0;
+
+ min = 1U << min;
+ max = 2U << max;
+ return (max - 1U) ^ (min - 1U);
+}
+
+#else
+
+static void
+check_singletons(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_EMERGENCY), 0x0001);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_ALERT), 0x0002);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_CRITICAL), 0x0004);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_ERROR), 0x0008);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_WARNING), 0x0010);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_NOTICE), 0x0020);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_INFO), 0x0040);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_TRACE), 0x0080);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_DEBUG), 0x0100);
+}
+
+static void
+check_custom(void)
+{
+ unsigned logmask = liblog_logmask__(LIBLOG_NOTICE + 1, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(logmask == 0x0020 || logmask == 0x0040);
+}
+
+static void
+check_from_emergency(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_ALERT), 0x0003U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_CRITICAL), 0x0007U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_ERROR), 0x000FU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_WARNING), 0x001FU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_NOTICE), 0x003FU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_INFO), 0x007FU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_TRACE), 0x00FFU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_EMERGENCY, LIBLOG_DEBUG), 0x01FFU);
+}
+
+static void
+check_from_alert(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_CRITICAL), 0x0006U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_ERROR), 0x000EU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_WARNING), 0x001EU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_NOTICE), 0x003EU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_INFO), 0x007EU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_TRACE), 0x00FEU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ALERT, LIBLOG_DEBUG), 0x01FEU);
+}
+
+static void
+check_from_critical(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_ERROR), 0x000CU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_WARNING), 0x001CU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_NOTICE), 0x003CU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_INFO), 0x007CU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_TRACE), 0x00FCU);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_CRITICAL, LIBLOG_DEBUG), 0x01FCU);
+}
+
+static void
+check_from_error(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_WARNING), 0x0018U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_NOTICE), 0x0038U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_INFO), 0x0078U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_TRACE), 0x00F8U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_ERROR, LIBLOG_DEBUG), 0x01F8U);
+}
+
+static void
+check_from_warning(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_ERROR), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_NOTICE), 0x0030U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_INFO), 0x0070U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_TRACE), 0x00F0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_WARNING, LIBLOG_DEBUG), 0x01F0U);
+}
+
+static void
+check_from_notice(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_ERROR), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_WARNING), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_INFO), 0x0060U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_TRACE), 0x00E0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_NOTICE, LIBLOG_DEBUG), 0x01E0U);
+}
+
+static void
+check_from_info(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_ERROR), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_WARNING), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_NOTICE), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_TRACE), 0x00C0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_INFO, LIBLOG_DEBUG), 0x01C0U);
+}
+
+static void
+check_from_trace(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_ERROR), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_WARNING), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_NOTICE), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_INFO), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_TRACE, LIBLOG_DEBUG), 0x0180U);
+}
+
+static void
+check_from_debug(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_EMERGENCY), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_ALERT), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_CRITICAL), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_ERROR), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_WARNING), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_NOTICE), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_INFO), 0U);
+ ASSERT_EQ_UINT(liblog_logmask__(LIBLOG_DEBUG, LIBLOG_TRACE), 0U);
+}
+
+static void
+check_negative(void)
+{
+ if ((enum liblog_level)-1 < 0)
+ ASSERT_EQ_UINT(liblog_logmask__((enum liblog_level)-10, (enum liblog_level)-10), 1U);
+}
+
+static void
+check_hyperverbose(void)
+{
+ ASSERT_EQ_UINT(liblog_logmask__((enum liblog_level)0x7FFF, (enum liblog_level)0x7FFF), 0x0100U);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_from_emergency();
+ check_from_alert();
+ check_from_critical();
+ check_from_error();
+ check_from_warning();
+ check_from_notice();
+ check_from_info();
+ check_from_trace();
+ check_from_debug();
+ check_negative();
+ check_hyperverbose();
+ return 0;
+}
+
+#endif
diff --git a/liblog_mask_level.c b/liblog_mask_level.c
new file mode 100644
index 0000000..5989f7d
--- /dev/null
+++ b/liblog_mask_level.c
@@ -0,0 +1,113 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_mask_level(struct liblog_context *, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0001U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0002U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0004U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0008U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0010U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0020U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0040U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0080U);
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == 0x0020U || ctx.logmask == 0x0040U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, 1U);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_level(&ctx, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+
+ liblog_mask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0001U);
+ liblog_mask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0001U);
+ liblog_mask_level(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0101U);
+ liblog_mask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0103U);
+ liblog_mask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0103U);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_mask_range.c b/liblog_mask_range.c
new file mode 100644
index 0000000..4eae18a
--- /dev/null
+++ b/liblog_mask_range.c
@@ -0,0 +1,412 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_mask_range(struct liblog_context *, enum liblog_level, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0001U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0002U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0004U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0008U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0010U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0020U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0040U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0080U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE + 1, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == 0x0020U || ctx.logmask == 0x0040U);
+}
+
+static void
+check_from_emergency(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0003U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0007U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x000FU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x001FU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x003FU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x007FU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00FFU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FFU);
+}
+
+static void
+check_from_alert(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0006U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x000EU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x001EU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x003EU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x007EU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00FEU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ALERT, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FEU);
+}
+
+static void
+check_from_critical(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x000CU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x001CU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x003CU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x007CU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00FCU);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FCU);
+}
+
+static void
+check_from_error(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0018U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0038U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0078U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00F8U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_ERROR, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F8U);
+}
+
+static void
+check_from_warning(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0030U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0070U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00F0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_WARNING, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F0U);
+}
+
+static void
+check_from_notice(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0060U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00E0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_NOTICE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01E0U);
+}
+
+static void
+check_from_info(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x00C0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_INFO, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01C0U);
+}
+
+static void
+check_from_trace(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0180U);
+}
+
+static void
+check_from_debug(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, (enum liblog_level)-10, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, 1U);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, (enum liblog_level)0x7FFF, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+
+ ctx.logmask = 0U;
+ liblog_mask_range(&ctx, (enum liblog_level)0, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FFU);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0001U);
+ liblog_mask_range(&ctx, LIBLOG_TRACE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0181U);
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0183U);
+ liblog_mask_range(&ctx, LIBLOG_DEBUG, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0183U);
+ liblog_mask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FFU);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_from_emergency();
+ check_from_alert();
+ check_from_critical();
+ check_from_error();
+ check_from_warning();
+ check_from_notice();
+ check_from_info();
+ check_from_trace();
+ check_from_debug();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_mask_verbose.c b/liblog_mask_verbose.c
new file mode 100644
index 0000000..3218b66
--- /dev/null
+++ b/liblog_mask_verbose.c
@@ -0,0 +1,111 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_mask_verbose(struct liblog_context *, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FFU);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FEU);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FCU);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F8U);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01E0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01C0U);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0180U);
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == 0x01E0U || ctx.logmask == 0x01C0U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01FFU);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+ liblog_mask_verbose(&ctx, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = 0U;
+
+ liblog_mask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x0100U);
+ liblog_mask_verbose(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F8U);
+ liblog_mask_verbose(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F8U);
+ liblog_mask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, 0x01F8U);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_notice.c b/liblog_notice.c
new file mode 100644
index 0000000..28d9f0e
--- /dev/null
+++ b/liblog_notice.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_notice(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_notice liblog__dont_want__
+# include "common.h"
+# undef liblog_notice
+
+LIBLOG_PRINTF__(2) int
+liblog_notice(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_NOTICE, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-notice.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_notice_cork.c b/liblog_notice_cork.c
new file mode 100644
index 0000000..66e653f
--- /dev/null
+++ b/liblog_notice_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_notice_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_notice_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_notice_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_notice_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_NOTICE, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-notice.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_trace.c b/liblog_trace.c
new file mode 100644
index 0000000..b44d5b1
--- /dev/null
+++ b/liblog_trace.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_trace(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_trace liblog__dont_want__
+# include "common.h"
+# undef liblog_trace
+
+LIBLOG_PRINTF__(2) int
+liblog_trace(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_TRACE, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-trace.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_trace__.c b/liblog_trace__.c
new file mode 100644
index 0000000..3ed9462
--- /dev/null
+++ b/liblog_trace__.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+#ifdef WEAK_LINKING_FAILED
+# warning Do not know how to create weak linking, backtrace (liblog_trace__) will not be replacable
+#endif
+
+__attribute__((__const__))
+int
+liblog_trace__(char **textp, size_t *offsetp, size_t *allocsizep, size_t skip, void *saved_trace) /* (TODO impl) */
+{
+ (void) textp;
+ (void) offsetp;
+ (void) allocsizep;
+ (void) skip;
+ (void) saved_trace;
+ return 0;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_trace_cork.c b/liblog_trace_cork.c
new file mode 100644
index 0000000..c8713d7
--- /dev/null
+++ b/liblog_trace_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_trace_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_trace_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_trace_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_trace_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_TRACE, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-trace.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_uncork.c b/liblog_uncork.c
new file mode 100644
index 0000000..3e52f75
--- /dev/null
+++ b/liblog_uncork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_uncork(struct liblog_context *);
+
+# else
+# define liblog_uncork liblog__dont_want__
+# include "common.h"
+# undef liblog_uncork
+
+int
+liblog_uncork(struct liblog_context *ctx)
+{ return liblog_xlog(ctx, 0, LIBLOG_XLOG_NO_BACKTRACE | LIBLOG_XLOG_NO_TEXT | LIBLOG_XLOG_UNCORK | XLOG_NOT_INLINE, NULL); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_unmask_level.c b/liblog_unmask_level.c
new file mode 100644
index 0000000..9d3f691
--- /dev/null
+++ b/liblog_unmask_level.c
@@ -0,0 +1,113 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_unmask_level(struct liblog_context *, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0001U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0002U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0004U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0008U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0010U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0020U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0040U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0080U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == ~0x0020U || ctx.logmask == ~0x0040U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, ~1U);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_level(&ctx, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+
+ liblog_unmask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0001U);
+ liblog_unmask_level(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0001U);
+ liblog_unmask_level(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0101U);
+ liblog_unmask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0103U);
+ liblog_unmask_level(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0103U);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_unmask_range.c b/liblog_unmask_range.c
new file mode 100644
index 0000000..62b7895
--- /dev/null
+++ b/liblog_unmask_range.c
@@ -0,0 +1,412 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_unmask_range(struct liblog_context *, enum liblog_level, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0001U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0002U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0004U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0008U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0010U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0020U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0040U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0080U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE + 1, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == ~0x0020U || ctx.logmask == ~0x0040U);
+}
+
+static void
+check_from_emergency(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0003U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0007U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x000FU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x001FU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x003FU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x007FU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00FFU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FFU);
+}
+
+static void
+check_from_alert(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0006U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x000EU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x001EU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x003EU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x007EU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00FEU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ALERT, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FEU);
+}
+
+static void
+check_from_critical(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x000CU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x001CU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x003CU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x007CU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00FCU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_CRITICAL, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FCU);
+}
+
+static void
+check_from_error(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0018U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0038U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0078U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00F8U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_ERROR, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F8U);
+}
+
+static void
+check_from_warning(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0030U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0070U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00F0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_WARNING, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F0U);
+}
+
+static void
+check_from_notice(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0060U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00E0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_NOTICE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01E0U);
+}
+
+static void
+check_from_info(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x00C0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_INFO, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01C0U);
+}
+
+static void
+check_from_trace(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0180U);
+}
+
+static void
+check_from_debug(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, (enum liblog_level)-10, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, ~1U);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, (enum liblog_level)0x7FFF, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_range(&ctx, (enum liblog_level)0, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FFU);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0001U);
+ liblog_unmask_range(&ctx, LIBLOG_TRACE, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0181U);
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0183U);
+ liblog_unmask_range(&ctx, LIBLOG_DEBUG, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0183U);
+ liblog_unmask_range(&ctx, LIBLOG_EMERGENCY, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FFU);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_from_emergency();
+ check_from_alert();
+ check_from_critical();
+ check_from_error();
+ check_from_warning();
+ check_from_notice();
+ check_from_info();
+ check_from_trace();
+ check_from_debug();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_unmask_verbose.c b/liblog_unmask_verbose.c
new file mode 100644
index 0000000..67ac221
--- /dev/null
+++ b/liblog_unmask_verbose.c
@@ -0,0 +1,111 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline void liblog_unmask_verbose(struct liblog_context *, enum liblog_level);
+
+#else
+
+static void
+check_singletons(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_EMERGENCY);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FFU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_ALERT);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FEU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_CRITICAL);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FCU);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F8U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_WARNING);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_NOTICE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01E0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01C0U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_TRACE);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0180U);
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+}
+
+static void
+check_custom(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, LIBLOG_NOTICE + 1);
+ ASSERT_IS_TRUE(ctx.logmask == ~0x01E0U || ctx.logmask == ~0x01C0U);
+}
+
+static void
+check_negative(void)
+{
+ struct liblog_context ctx;
+
+ if ((enum liblog_level)-1 < 0) {
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, (enum liblog_level)-10);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01FFU);
+ }
+}
+
+static void
+check_hyperverbose(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+ liblog_unmask_verbose(&ctx, (enum liblog_level)0x7FFF);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+}
+
+static void
+check_series(void)
+{
+ struct liblog_context ctx;
+
+ ctx.logmask = ~0U;
+
+ liblog_unmask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x0100U);
+ liblog_unmask_verbose(&ctx, LIBLOG_ERROR);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F8U);
+ liblog_unmask_verbose(&ctx, LIBLOG_INFO);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F8U);
+ liblog_unmask_verbose(&ctx, LIBLOG_DEBUG);
+ ASSERT_EQ_UINT(ctx.logmask, ~0x01F8U);
+}
+
+int
+main(void)
+{
+ check_singletons();
+ check_custom();
+ check_negative();
+ check_hyperverbose();
+ check_series();
+ return 0;
+}
+
+#endif
diff --git a/liblog_use_fd.c b/liblog_use_fd.c
new file mode 100644
index 0000000..52687c7
--- /dev/null
+++ b/liblog_use_fd.c
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_fd(struct liblog_context *, int, const char *, int);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_fd_for_range.c b/liblog_use_fd_for_range.c
new file mode 100644
index 0000000..8460d5e
--- /dev/null
+++ b/liblog_use_fd_for_range.c
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_fd_for_range(struct liblog_context *, int, const char *, int,
+ enum liblog_level, enum liblog_level);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_file.c b/liblog_use_file.c
new file mode 100644
index 0000000..3083f1a
--- /dev/null
+++ b/liblog_use_file.c
@@ -0,0 +1,32 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+struct liblog_output *
+liblog_use_file(struct liblog_context *ctx, int dirfd, const char *path, const char *prefixfmt, int use_backtrace)
+{
+ struct liblog_output *ret;
+ int fd;
+ if (!path || !*path)
+ return liblog_use_fd(ctx, dirfd, prefixfmt, use_backtrace);
+ if (dirfd == -1 && *path != '/') {
+ errno = EINVAL;
+ return NULL;
+ }
+ fd = openat(dirfd, path, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (fd < 0)
+ return NULL;
+ ret = liblog_use_fd(ctx, fd, prefixfmt, use_backtrace);
+ if (!ret) {
+ close(fd);
+ return NULL;
+ }
+ ret->sink.file.owns_fd = 1;
+ return ret;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_file_for_range.c b/liblog_use_file_for_range.c
new file mode 100644
index 0000000..e312c4b
--- /dev/null
+++ b/liblog_use_file_for_range.c
@@ -0,0 +1,33 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+struct liblog_output *
+liblog_use_file_for_range(struct liblog_context *ctx, int dirfd, const char *path, const char *prefixfmt,
+ int use_backtrace, enum liblog_level least_verbose, enum liblog_level most_verbose)
+{
+ struct liblog_output *ret;
+ int fd;
+ if (!path || !*path)
+ return liblog_use_fd_for_range(ctx, dirfd, prefixfmt, use_backtrace, least_verbose, most_verbose);
+ if (dirfd == -1 && *path != '/') {
+ errno = EINVAL;
+ return NULL;
+ }
+ fd = openat(dirfd, path, O_WRONLY | O_CREAT | O_APPEND, 0644);
+ if (fd < 0)
+ return NULL;
+ ret = liblog_use_fd_for_range(ctx, fd, prefixfmt, use_backtrace, least_verbose, most_verbose);
+ if (!ret) {
+ close(fd);
+ return NULL;
+ }
+ ret->sink.file.owns_fd = 1;
+ return ret;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_output.c b/liblog_use_output.c
new file mode 100644
index 0000000..3471722
--- /dev/null
+++ b/liblog_use_output.c
@@ -0,0 +1,54 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+struct liblog_output *
+liblog_use_output(struct liblog_context *ctx, const struct liblog_output *output)
+{
+ void *new;
+
+ if (!ctx || !output)
+ goto einval;
+
+ if (output->lowest_verbosity_unlimited && output->highest_verbosity_unlimited)
+ if (output->lowest_verbosity > output->highest_verbosity)
+ goto einval;
+
+ if (output->sink_type == LIBLOG_STREAM) {
+ if (!output->sink.stream.stream)
+ goto einval;
+ } else if (output->sink_type != LIBLOG_SYSLOG && output->sink_type != LIBLOG_FILE) {
+ /*
+ * We do not want to validate syslog level,
+ * nor do we want to validate file descriptor,
+ * indeed the user may want to output to a negative
+ * file descriptor so that it only visible during
+ * system call tracing
+ */
+ einval:
+ errno = EINVAL;
+ return NULL;
+ }
+
+ if (ctx->noutputs > SIZE_MAX / sizeof(*ctx->outputs) - 1)
+ goto enomem;
+ new = realloc(ctx->outputs, (ctx->noutputs + 1) * sizeof(*ctx->outputs));
+ if (!new) {
+ enomem:
+ errno = ENOMEM;
+ return NULL;
+ }
+
+ memcpy(&ctx->outputs[ctx->noutputs], output, sizeof(*output));
+
+ if (!ctx->outputs[ctx->noutputs].prefixfmt)
+ ctx->outputs[ctx->noutputs].prefixfmt = DEFAULT_PREFIXFMT;
+
+ return &ctx->outputs[ctx->noutputs++];
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_stderr.c b/liblog_use_stderr.c
new file mode 100644
index 0000000..f66d953
--- /dev/null
+++ b/liblog_use_stderr.c
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_stderr(struct liblog_context *, const char *, int);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_stderr_for_range.c b/liblog_use_stderr_for_range.c
new file mode 100644
index 0000000..1ffaee1
--- /dev/null
+++ b/liblog_use_stderr_for_range.c
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_stderr_for_range(struct liblog_context *, const char *, int,
+ enum liblog_level, enum liblog_level);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_stream.c b/liblog_use_stream.c
new file mode 100644
index 0000000..c415627
--- /dev/null
+++ b/liblog_use_stream.c
@@ -0,0 +1,11 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_stream(struct liblog_context *, FILE *, const char *, int);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_stream_for_range.c b/liblog_use_stream_for_range.c
new file mode 100644
index 0000000..6749b26
--- /dev/null
+++ b/liblog_use_stream_for_range.c
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_stream_for_range(struct liblog_context *, FILE *, const char *, int,
+ enum liblog_level, enum liblog_level);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_syslog.c b/liblog_use_syslog.c
new file mode 100644
index 0000000..09b6ca9
--- /dev/null
+++ b/liblog_use_syslog.c
@@ -0,0 +1,99 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+#if defined(__clang__)
+# pragma clang diagnostic ignored "-Wassign-enum"
+#endif
+
+int
+liblog_use_syslog(struct liblog_context *ctx, const char *prefixfmt, int use_backtrace)
+{
+ void *new;
+ size_t old_noutputs = ctx->noutputs;
+ struct liblog_output output = {
+ .userdata = NULL,
+ .prefixfmt = prefixfmt,
+ .lowest_verbosity_unlimited = 1U,
+ .highest_verbosity_unlimited = 0U,
+ .use_backtrace = (use_backtrace ? 1U : 0U),
+ .lowest_verbosity = 0,
+ .highest_verbosity = LIBLOG_ALERT - 1,
+ .sink_type = LIBLOG_SYSLOG,
+ .sink.syslog.level = LOG_EMERG
+ };
+
+ if (!ctx) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (ctx->noutputs > SIZE_MAX / sizeof(*ctx->outputs) - 8)
+ goto enomem;
+ new = realloc(ctx->outputs, (ctx->noutputs + 8) * sizeof(*ctx->outputs));
+ if (!new) {
+ enomem:
+ errno = ENOMEM;
+ return -1;
+ }
+ ctx->outputs = new;
+
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity_unlimited = 0;
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_CRITICAL - 1;
+ output.sink.syslog.level = LOG_ALERT;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_ERROR - 1;
+ output.sink.syslog.level = LOG_CRIT;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_WARNING - 1;
+ output.sink.syslog.level = LOG_ERR;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_NOTICE - 1;
+ output.sink.syslog.level = LOG_WARNING;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_INFO - 1;
+ output.sink.syslog.level = LOG_NOTICE;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity = LIBLOG_TRACE - 1;
+ output.sink.syslog.level = LOG_INFO;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ output.lowest_verbosity = output.highest_verbosity + 1;
+ output.highest_verbosity_unlimited = 1;
+ output.sink.syslog.level = LOG_DEBUG;
+ if (liblog_use_output(ctx, &output))
+ goto fail;
+
+ return 0;
+
+fail:
+ while (ctx->noutputs > old_noutputs)
+ liblog_destroy_output(&ctx->outputs[--ctx->noutputs]);
+ return -1;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_use_syslog_for_range.c b/liblog_use_syslog_for_range.c
new file mode 100644
index 0000000..014b6fa
--- /dev/null
+++ b/liblog_use_syslog_for_range.c
@@ -0,0 +1,12 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+extern inline struct liblog_output *liblog_use_syslog_for_range(struct liblog_context *, int, const char *, int,
+ enum liblog_level, enum liblog_level);
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_valert.c b/liblog_valert.c
new file mode 100644
index 0000000..308fa8d
--- /dev/null
+++ b/liblog_valert.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_valert(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_valert liblog__dont_want__
+# include "common.h"
+# undef liblog_valert
+
+LIBLOG_VPRINTF__(2) int
+liblog_valert(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_ALERT, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-alert.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_valert_cork.c b/liblog_valert_cork.c
new file mode 100644
index 0000000..288b098
--- /dev/null
+++ b/liblog_valert_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_valert_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_valert_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_valert_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_valert_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_ALERT, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-alert.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vcritical.c b/liblog_vcritical.c
new file mode 100644
index 0000000..0c921e9
--- /dev/null
+++ b/liblog_vcritical.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vcritical(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vcritical liblog__dont_want__
+# include "common.h"
+# undef liblog_vcritical
+
+LIBLOG_VPRINTF__(2) int
+liblog_vcritical(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_CRITICAL, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-critical.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vcritical_cork.c b/liblog_vcritical_cork.c
new file mode 100644
index 0000000..1467c4b
--- /dev/null
+++ b/liblog_vcritical_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vcritical_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vcritical_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vcritical_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vcritical_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_CRITICAL, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-critical.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vdebug.c b/liblog_vdebug.c
new file mode 100644
index 0000000..fe2d276
--- /dev/null
+++ b/liblog_vdebug.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vdebug(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vdebug liblog__dont_want__
+# include "common.h"
+# undef liblog_vdebug
+
+LIBLOG_VPRINTF__(2) int
+liblog_vdebug(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_DEBUG, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-debug.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vdebug_cork.c b/liblog_vdebug_cork.c
new file mode 100644
index 0000000..e6bde45
--- /dev/null
+++ b/liblog_vdebug_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vdebug_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vdebug_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vdebug_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vdebug_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_DEBUG, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-debug.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vemergency.c b/liblog_vemergency.c
new file mode 100644
index 0000000..b194f6d
--- /dev/null
+++ b/liblog_vemergency.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vemergency(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vemergency liblog__dont_want__
+# include "common.h"
+# undef liblog_vemergency
+
+LIBLOG_VPRINTF__(2) int
+liblog_vemergency(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_EMERGENCY, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-emergency.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vemergency_cork.c b/liblog_vemergency_cork.c
new file mode 100644
index 0000000..1d91fcb
--- /dev/null
+++ b/liblog_vemergency_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vemergency_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vemergency_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vemergency_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vemergency_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_EMERGENCY, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-emergency.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_verror.c b/liblog_verror.c
new file mode 100644
index 0000000..503caed
--- /dev/null
+++ b/liblog_verror.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_verror(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_verror liblog__dont_want__
+# include "common.h"
+# undef liblog_verror
+
+LIBLOG_VPRINTF__(2) int
+liblog_verror(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_ERROR, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-error.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_verror_cork.c b/liblog_verror_cork.c
new file mode 100644
index 0000000..5903ee9
--- /dev/null
+++ b/liblog_verror_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_verror_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_verror_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_verror_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_verror_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_ERROR, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-error.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vinfo.c b/liblog_vinfo.c
new file mode 100644
index 0000000..7db76f2
--- /dev/null
+++ b/liblog_vinfo.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vinfo(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vinfo liblog__dont_want__
+# include "common.h"
+# undef liblog_vinfo
+
+LIBLOG_VPRINTF__(2) int
+liblog_vinfo(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_INFO, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-info.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vinfo_cork.c b/liblog_vinfo_cork.c
new file mode 100644
index 0000000..b4150c6
--- /dev/null
+++ b/liblog_vinfo_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vinfo_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vinfo_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vinfo_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vinfo_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_INFO, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-info.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vlog.c b/liblog_vlog.c
new file mode 100644
index 0000000..7396fc1
--- /dev/null
+++ b/liblog_vlog.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vlog(struct liblog_context *, enum liblog_level, const char *, va_list);
+
+# else
+# define liblog_vlog liblog__dont_want__
+# include "common.h"
+# undef liblog_vlog
+
+LIBLOG_VPRINTF__(3) int
+liblog_vlog(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, level, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_vlog_cork.c b/liblog_vlog_cork.c
new file mode 100644
index 0000000..f7bf4e9
--- /dev/null
+++ b/liblog_vlog_cork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vlog_cork(struct liblog_context *, enum liblog_level, const char *, va_list);
+
+# else
+# define liblog_vlog_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vlog_cork
+
+LIBLOG_VPRINTF__(3) int
+liblog_vlog_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_vlog_no_backtrace.c b/liblog_vlog_no_backtrace.c
new file mode 100644
index 0000000..c545f15
--- /dev/null
+++ b/liblog_vlog_no_backtrace.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vlog_no_backtrace(struct liblog_context *, enum liblog_level, const char *, va_list);
+
+# else
+# define liblog_vlog_no_backtrace liblog__dont_want__
+# include "common.h"
+# undef liblog_vlog_no_backtrace
+
+LIBLOG_VPRINTF__(3) int
+liblog_vlog_no_backtrace(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, level, LIBLOG_XLOG_NO_BACKTRACE | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_vlog_no_backtrace_cork.c b/liblog_vlog_no_backtrace_cork.c
new file mode 100644
index 0000000..a18b220
--- /dev/null
+++ b/liblog_vlog_no_backtrace_cork.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vlog_no_backtrace_cork(struct liblog_context *, enum liblog_level, const char *, va_list);
+
+# else
+# define liblog_vlog_no_backtrace_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vlog_no_backtrace_cork
+
+LIBLOG_VPRINTF__(3) int
+liblog_vlog_no_backtrace_cork(struct liblog_context *ctx, enum liblog_level level, const char *fmt, va_list args)
+{ return liblog_xlog(ctx, level, LIBLOG_XLOG_NO_BACKTRACE | LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_vnotice.c b/liblog_vnotice.c
new file mode 100644
index 0000000..2c6bc79
--- /dev/null
+++ b/liblog_vnotice.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vnotice(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vnotice liblog__dont_want__
+# include "common.h"
+# undef liblog_vnotice
+
+LIBLOG_VPRINTF__(2) int
+liblog_vnotice(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_NOTICE, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-notice.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vnotice_cork.c b/liblog_vnotice_cork.c
new file mode 100644
index 0000000..1c14e9e
--- /dev/null
+++ b/liblog_vnotice_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vnotice_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vnotice_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vnotice_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vnotice_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_NOTICE, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-notice.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vtrace.c b/liblog_vtrace.c
new file mode 100644
index 0000000..7f8c025
--- /dev/null
+++ b/liblog_vtrace.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vtrace(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vtrace liblog__dont_want__
+# include "common.h"
+# undef liblog_vtrace
+
+LIBLOG_VPRINTF__(2) int
+liblog_vtrace(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_TRACE, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-trace.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vtrace_cork.c b/liblog_vtrace_cork.c
new file mode 100644
index 0000000..0e5a38c
--- /dev/null
+++ b/liblog_vtrace_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vtrace_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vtrace_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vtrace_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vtrace_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_TRACE, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-trace.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vwarning.c b/liblog_vwarning.c
new file mode 100644
index 0000000..74c2c68
--- /dev/null
+++ b/liblog_vwarning.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vwarning(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vwarning liblog__dont_want__
+# include "common.h"
+# undef liblog_vwarning
+
+LIBLOG_VPRINTF__(2) int
+liblog_vwarning(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_WARNING, XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-warning.h"
+# include "test-vlevel.c"
+#endif
diff --git a/liblog_vwarning_cork.c b/liblog_vwarning_cork.c
new file mode 100644
index 0000000..4200517
--- /dev/null
+++ b/liblog_vwarning_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_vwarning_cork(struct liblog_context *, const char *, va_list);
+
+# else
+# define liblog_vwarning_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_vwarning_cork
+
+LIBLOG_VPRINTF__(2) int
+liblog_vwarning_cork(struct liblog_context *ctx, const char *fmt, va_list args)
+{ return liblog_vxlog(ctx, LIBLOG_WARNING, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt, args); }
+
+# endif
+
+#else
+# include "test-warning.h"
+# include "test-vlevel_cork.c"
+#endif
diff --git a/liblog_vxlog.c b/liblog_vxlog.c
new file mode 100644
index 0000000..4093c59
--- /dev/null
+++ b/liblog_vxlog.c
@@ -0,0 +1,121 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wformat-nonliteral"
+#endif
+
+static int
+ensure_fit(struct messagebuf *msg, size_t extent)
+{
+ size_t new_size;
+ void *new;
+ if (extent >= msg->size - msg->len) {
+ new_size = msg->len + extent + 1;
+ new = realloc(msg->text, new_size);
+ if (!new)
+ return -1;
+ msg->text = new;
+ msg->size = new_size;
+ }
+ return 0;
+}
+
+int
+liblog_vxlog(struct liblog_context *ctx, enum liblog_level level, unsigned flags, const char *fmt, va_list args)
+{
+ /* TODO optionally avoid allocating and deallocating for each call */
+ /* TODO respect logmask */
+
+ struct messagebuf msgbuf = MESSAGEBUF_INIT, *msg;
+ int corked;
+ size_t old_len;
+
+ if (!ctx || (level & (enum liblog_level)~0x001F)) {
+ einval:
+ errno = EINVAL;
+ return -1;
+ }
+ if (!fmt && !(flags & (XLOG_NOT_INLINE - 1U) & LIBLOG_XLOG_NO_TEXT))
+ goto einval;
+ if ((flags & (LIBLOG_XLOG_CORK | LIBLOG_XLOG_UNCORK)) == (LIBLOG_XLOG_CORK | LIBLOG_XLOG_UNCORK))
+ goto einval;
+ if ((flags & (LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_BACKTRACE)) == (LIBLOG_XLOG_BACKTRACE | LIBLOG_XLOG_NO_BACKTRACE))
+ goto einval;
+
+ msg = ctx->internal_state ? &ctx->internal_state->msg : &msgbuf;
+ corked = !!msg->prefix;
+ old_len = msg->len;
+
+ /* TODO ctx->internal_state must be allocated */
+
+ if (!msg->prefix) {
+ /* TODO implement: prefix */
+ }
+
+ if (!(flags & LIBLOG_XLOG_NO_BACKTRACE)) {
+ if (flags & LIBLOG_XLOG_BACKTRACE) {
+ unsigned levels = (flags / XLOG_NOT_INLINE) + 1U;
+ if (liblog_trace__(&msg->text, &msg->len, &msg->size, (size_t)levels, NULL))
+ return -1;
+ }
+ }
+
+ if (!(flags & LIBLOG_XLOG_NO_TEXT)) {
+ int len;
+ va_list args2;
+ va_copy(args2, args);
+ len = snprintf(NULL, 0, fmt, args2);
+ va_end(args2);
+ if (len < 0)
+ return -1;
+ if (ensure_fit(msg, (size_t)len))
+ return -1;
+ if (snprintf(&msg->text[msg->len], (size_t)len + 1, fmt, args) != len)
+ abort();
+ msg->len += (size_t)len;
+ }
+
+ if (flags & LIBLOG_XLOG_UNCORK) {
+ if (msg == &ctx->internal_state->msg) {
+ memcpy(&msgbuf, msg, sizeof(*msg));
+ ctx->internal_state->msg = MESSAGEBUF_INIT;
+ msg = &msgbuf;
+ }
+ goto output;
+ } else if (flags & LIBLOG_XLOG_CORK) {
+ if (msg != &ctx->internal_state->msg)
+ memcpy(&ctx->internal_state->msg, msg, sizeof(*msg));
+ } else if (corked) {
+ /* do nothing, internal state already set */
+ } else {
+ output:
+ if (msg->len && msg->text[msg->len - 1] != '\n') {
+ if (ensure_fit(msg, 0))
+ goto fail;
+ msg->text[msg->len++] = '\n';
+ }
+ if (liblog_flush__(ctx, msg))
+ goto fail;
+ free(msg->prefix);
+ free(msg->text);
+ *msg = MESSAGEBUF_INIT;
+ }
+
+ return 0;
+
+fail:
+ if (msg != &ctx->internal_state->msg) {
+ free(msg->prefix);
+ free(msg->text);
+ }
+ msg->len = old_len;
+ return -1;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_warning.c b/liblog_warning.c
new file mode 100644
index 0000000..abdbc69
--- /dev/null
+++ b/liblog_warning.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_warning(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_warning liblog__dont_want__
+# include "common.h"
+# undef liblog_warning
+
+LIBLOG_PRINTF__(2) int
+liblog_warning(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_WARNING, XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-warning.h"
+# include "test-level.c"
+#endif
diff --git a/liblog_warning_cork.c b/liblog_warning_cork.c
new file mode 100644
index 0000000..6630ff1
--- /dev/null
+++ b/liblog_warning_cork.c
@@ -0,0 +1,23 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_warning_cork(struct liblog_context *, const char *, ...);
+
+# else
+# define liblog_warning_cork liblog__dont_want__
+# include "common.h"
+# undef liblog_warning_cork
+
+LIBLOG_PRINTF__(2) int
+liblog_warning_cork(struct liblog_context *ctx, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, LIBLOG_WARNING, LIBLOG_XLOG_CORK | XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "test-warning.h"
+# include "test-level_cork.c"
+#endif
diff --git a/liblog_whence__.c b/liblog_whence__.c
new file mode 100644
index 0000000..767987d
--- /dev/null
+++ b/liblog_whence__.c
@@ -0,0 +1,24 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+#ifndef TEST
+
+#ifdef WEAK_LINKING_FAILED
+# warning Do not know how to create weak linking, source determination (liblog_whence__) will not be replacable
+#endif
+
+int
+liblog_whence__(char **file_out, off_t *line_out, char **function_out, size_t skip, void **trace_save_out) /* (TODO impl) */
+{
+ *file_out = NULL;
+ *line_out = -1;
+ *function_out = NULL;
+ (void) skip;
+ *trace_save_out = NULL;
+ return 0;
+}
+
+#else
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/liblog_xlog.c b/liblog_xlog.c
new file mode 100644
index 0000000..12640ef
--- /dev/null
+++ b/liblog_xlog.c
@@ -0,0 +1,25 @@
+/* See LICENSE file for copyright and license details. */
+#ifndef TEST
+
+# ifdef USE_EXTERN_INLINE
+# include "common.h"
+
+extern inline int liblog_xlog(struct liblog_context *, enum liblog_level, unsigned, const char *, ...);
+
+# else
+# define liblog_xlog liblog__dont_want__
+# include "common.h"
+# undef liblog_xlog
+
+LIBLOG_PRINTF__(4) int
+liblog_xlog(struct liblog_context *ctx, enum liblog_level level, unsigned flags, const char *fmt, ...)
+{ LIBLOG_VA__(liblog_vxlog, ctx, level, flags + XLOG_NOT_INLINE, fmt); }
+
+# endif
+
+#else
+# include "common.h"
+
+int main(void) {return 0;} /* TODO test */
+
+#endif
diff --git a/mk/linux.mk b/mk/linux.mk
new file mode 100644
index 0000000..ad58f69
--- /dev/null
+++ b/mk/linux.mk
@@ -0,0 +1,6 @@
+LIBEXT = so
+LIBFLAGS = -shared -Wl,-soname,lib$(LIB_NAME).$(LIBEXT).$(LIB_MAJOR)
+LIBMAJOREXT = $(LIBEXT).$(LIB_MAJOR)
+LIBMINOREXT = $(LIBEXT).$(LIB_VERSION)
+
+FIX_INSTALL_NAME = :
diff --git a/mk/macos.mk b/mk/macos.mk
new file mode 100644
index 0000000..3d19dde
--- /dev/null
+++ b/mk/macos.mk
@@ -0,0 +1,6 @@
+LIBEXT = dylib
+LIBFLAGS = -dynamiclib -Wl,-compatibility_version,$(LIB_MAJOR) -Wl,-current_version,$(LIB_VERSION)
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = install_name_tool -id "$(PREFIX)/lib/liblog.$(LIBMAJOREXT)"
diff --git a/mk/windows.mk b/mk/windows.mk
new file mode 100644
index 0000000..ed5ec8d
--- /dev/null
+++ b/mk/windows.mk
@@ -0,0 +1,6 @@
+LIBEXT = dll
+LIBFLAGS = -shared
+LIBMAJOREXT = $(LIB_MAJOR).$(LIBEXT)
+LIBMINOREXT = $(LIB_VERSION).$(LIBEXT)
+
+FIX_INSTALL_NAME = :
diff --git a/test-alert.h b/test-alert.h
new file mode 100644
index 0000000..62b12b4
--- /dev/null
+++ b/test-alert.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_ALERT
+#define test_level liblog_alert
+#define test_vlevel liblog_valert
+#define test_level_cork liblog_alert_cork
+#define test_vlevel_cork liblog_valert_cork
diff --git a/test-critical.h b/test-critical.h
new file mode 100644
index 0000000..8730c67
--- /dev/null
+++ b/test-critical.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_CRITICAL
+#define test_level liblog_critical
+#define test_vlevel liblog_vcritical
+#define test_level_cork liblog_critical_cork
+#define test_vlevel_cork liblog_vcritical_cork
diff --git a/test-debug.h b/test-debug.h
new file mode 100644
index 0000000..fdbe009
--- /dev/null
+++ b/test-debug.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_DEBUG
+#define test_level liblog_debug
+#define test_vlevel liblog_vdebug
+#define test_level_cork liblog_debug_cork
+#define test_vlevel_cork liblog_vdebug_cork
diff --git a/test-emergency.h b/test-emergency.h
new file mode 100644
index 0000000..22630a3
--- /dev/null
+++ b/test-emergency.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_EMERGENCY
+#define test_level liblog_emergency
+#define test_vlevel liblog_vemergency
+#define test_level_cork liblog_emergency_cork
+#define test_vlevel_cork liblog_vemergency_cork
diff --git a/test-error.h b/test-error.h
new file mode 100644
index 0000000..8e529c9
--- /dev/null
+++ b/test-error.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_ERROR
+#define test_level liblog_error
+#define test_vlevel liblog_verror
+#define test_level_cork liblog_error_cork
+#define test_vlevel_cork liblog_verror_cork
diff --git a/test-info.h b/test-info.h
new file mode 100644
index 0000000..3d62454
--- /dev/null
+++ b/test-info.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_INFO
+#define test_level liblog_info
+#define test_vlevel liblog_vinfo
+#define test_level_cork liblog_info_cork
+#define test_vlevel_cork liblog_vinfo_cork
diff --git a/test-level.c b/test-level.c
new file mode 100644
index 0000000..ec134ca
--- /dev/null
+++ b/test-level.c
@@ -0,0 +1,4 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+int main(void) {return 0;} /* TODO test */
diff --git a/test-level_cork.c b/test-level_cork.c
new file mode 100644
index 0000000..ec134ca
--- /dev/null
+++ b/test-level_cork.c
@@ -0,0 +1,4 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+int main(void) {return 0;} /* TODO test */
diff --git a/test-notice.h b/test-notice.h
new file mode 100644
index 0000000..69a20ba
--- /dev/null
+++ b/test-notice.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_NOTICE
+#define test_level liblog_notice
+#define test_vlevel liblog_vnotice
+#define test_level_cork liblog_notice_cork
+#define test_vlevel_cork liblog_vnotice_cork
diff --git a/test-trace.h b/test-trace.h
new file mode 100644
index 0000000..d7ef3b6
--- /dev/null
+++ b/test-trace.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_TRACE
+#define test_level liblog_trace
+#define test_vlevel liblog_vtrace
+#define test_level_cork liblog_trace_cork
+#define test_vlevel_cork liblog_vtrace_cork
diff --git a/test-vlevel.c b/test-vlevel.c
new file mode 100644
index 0000000..ec134ca
--- /dev/null
+++ b/test-vlevel.c
@@ -0,0 +1,4 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+int main(void) {return 0;} /* TODO test */
diff --git a/test-vlevel_cork.c b/test-vlevel_cork.c
new file mode 100644
index 0000000..ec134ca
--- /dev/null
+++ b/test-vlevel_cork.c
@@ -0,0 +1,4 @@
+/* See LICENSE file for copyright and license details. */
+#include "common.h"
+
+int main(void) {return 0;} /* TODO test */
diff --git a/test-warning.h b/test-warning.h
new file mode 100644
index 0000000..de05a8d
--- /dev/null
+++ b/test-warning.h
@@ -0,0 +1,6 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST_LEVEL LIBLOG_WARNING
+#define test_level liblog_warning
+#define test_vlevel liblog_vwarning
+#define test_level_cork liblog_warning_cork
+#define test_vlevel_cork liblog_vwarning_cork
diff --git a/testhelp.c b/testhelp.c
new file mode 100644
index 0000000..74997eb
--- /dev/null
+++ b/testhelp.c
@@ -0,0 +1,366 @@
+/* See LICENSE file for copyright and license details. */
+#define TEST
+#include "common.h"
+
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic ignored "-Wswitch"
+# pragma GCC diagnostic ignored "-Wswitch-enum"
+#endif
+
+
+static char *
+quote(const char *s, char **out)
+{
+ char *ret, *p;
+ int safe = 1;
+ *out = p = ret = malloc(strlen(s) * 4 + 3);
+ if (!ret) {
+ fprintf(stderr, "ran out of memory during printing of assertion failure\n");
+ exit(1);
+ }
+ *p++ = '"';
+ for (; *s; s++) {
+ if (*s == '\r')
+ p = stpcpy(p, "\\r"), safe = 1;
+ else if (*s == '\t')
+ p = stpcpy(p, "\\t"), safe = 1;
+ else if (*s == '\a')
+ p = stpcpy(p, "\\a"), safe = 1;
+ else if (*s == '\f')
+ p = stpcpy(p, "\\f"), safe = 1;
+ else if (*s == '\v')
+ p = stpcpy(p, "\\v"), safe = 1;
+ else if (*s == '\b')
+ p = stpcpy(p, "\\b"), safe = 1;
+ else if (*s == '\n')
+ p = stpcpy(p, "\\n"), safe = 1;
+ else if (*s == '\"')
+ p = stpcpy(p, "\\\""), safe = 1;
+ else if (*s == '\\')
+ p = stpcpy(p, "\\\\"), safe = 1;
+ else if (*s < ' ' || *s >= 127)
+ p += sprintf(p, "\\x%02X", (unsigned char)*s), safe = 0;
+ else if (*s == '?' && p[-1] == '?')
+ p += sprintf(p, "\"\"?"), safe = 1;
+ else if (isxdigit(*s) && !safe)
+ p += sprintf(p, "\"\"%c", *s), safe = 1;
+ else
+ *p++ = *s, safe = 1;
+ }
+ *p++ = '"';
+ *p++ = '\0';
+ return ret;
+}
+
+
+#define CASE(HOW, OP)\
+ case HOW:\
+ if ((*have) OP (*expect))\
+ return;\
+ symbol = #OP;\
+ break
+
+
+#define CASE_LOGIC(HOW, OP1, OP2, OPERAND3_IS_EXPECT)\
+ case HOW:\
+ if (((*have) OP1 (*expect)) OP2 (OPERAND3_IS_EXPECT ? (*expect) : 0))\
+ return;\
+ symbol = #OP1;\
+ symbol2 = #OP2;\
+ operand3_is_expect = (OPERAND3_IS_EXPECT);\
+ break
+
+
+static void
+test_assert_uint(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const uintmax_t *have, const uintmax_t *expect)
+{
+ const char *symbol;
+ const char *symbol2 = NULL;
+ int operand3_is_expect;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ CASE(ASSERT_EQ, ==);
+ CASE(ASSERT_NE, !=);
+ CASE(ASSERT_LT, <);
+ CASE(ASSERT_LE, <=);
+ CASE(ASSERT_GT, >);
+ CASE(ASSERT_GE, >=);
+ CASE_LOGIC(ASSERT_CONTAINS_ALL, &, ==, 1);
+ CASE_LOGIC(ASSERT_CONTAINS_ANY, &, !=, 0);
+ CASE_LOGIC(ASSERT_CONTAINS_NOT_ALL, &, !=, 1);
+ CASE_LOGIC(ASSERT_CONTAINS_NOT_ANY, &, ==, 0);
+ CASE_LOGIC(ASSERT_IS_IN, &~, ==, 0);
+ CASE_LOGIC(ASSERT_NOT_IN, &~, !=, 0);
+ default:
+ abort();
+ }
+ if (!symbol2)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ju %s %ju\n",
+ file, line, have_string, symbol, expect_string, *have, symbol, *expect);
+ else if (!operand3_is_expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s 0, actualised as (%#jx %s %#jx) %s 0\n",
+ file, line, have_string, symbol, expect_string, symbol2, *have, symbol, *expect, symbol2);
+ else
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting ((%s) %s (%s)) %s (%s), actualised as (%#jx %s %#jx) %s %#jx\n",
+ file, line, have_string, symbol, expect_string, symbol2, expect_string,
+ *have, symbol, *expect, symbol2, *expect);
+ exit(1);
+}
+
+
+static void
+test_assert_int(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const intmax_t *have, const intmax_t *expect)
+{
+ const char *symbol;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ CASE(ASSERT_EQ, ==);
+ CASE(ASSERT_NE, !=);
+ CASE(ASSERT_LT, <);
+ CASE(ASSERT_LE, <=);
+ CASE(ASSERT_GT, >);
+ CASE(ASSERT_GE, >=);
+ default:
+ test_assert_uint(file, line, how, have_string, expect_string,
+ (const uintmax_t *)have, (const uintmax_t *)expect);
+ return;
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %ji %s %ji\n",
+ file, line, have_string, symbol, expect_string, *have, symbol, *expect);
+ exit(1);
+}
+
+
+#define PTR_CASE(HOW, OP)\
+ case HOW:\
+ if ((have) OP (expect))\
+ return;\
+ symbol = #OP;\
+ break
+
+
+static void
+test_assert_ptr(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const char *have, const char *expect)
+{
+ const char *symbol;
+ switch (how) {
+ case ASSERT_NOT_LT: how = ASSERT_NOT_GE; break;
+ case ASSERT_NOT_LE: how = ASSERT_NOT_GT; break;
+ case ASSERT_NOT_GT: how = ASSERT_NOT_LE; break;
+ case ASSERT_NOT_GE: how = ASSERT_NOT_LT; break;
+ }
+ switch (how) {
+ PTR_CASE(ASSERT_EQ, ==);
+ PTR_CASE(ASSERT_NE, !=);
+ PTR_CASE(ASSERT_LT, <);
+ PTR_CASE(ASSERT_LE, <=);
+ PTR_CASE(ASSERT_GT, >);
+ PTR_CASE(ASSERT_GE, >=);
+ default:
+ abort();
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting (%s) %s (%s), actualised as %p %s %p\n",
+ file, line, have_string, symbol, expect_string, have, symbol, expect);
+ exit(1);
+}
+
+
+#define STR_CASE(HOW, FUNC)\
+ case HOW:\
+ if (FUNC(have, expect))\
+ return;\
+ function = #FUNC;\
+ break
+
+#define SWAP_STR_CASE(HOW, NEW_HOW)\
+ case HOW:\
+ test_assert_str(file, line, how, expect_string, have_string, expect, have, rev);\
+ return
+
+
+static void
+test_assert_str(const char *file, int line, enum assert_how how, const char *have_string,
+ const char *expect_string, const char *have, const char *expect, int rev)
+{
+ const char *function;
+ size_t have_len;
+ size_t expect_len;
+ char *have_quote = NULL;
+ char *expect_quote = NULL;
+
+ if (!have && !expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) and (%s) are NULL\n",
+ file, line, have_string, expect_string);
+ else if (!have)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
+ file, line, have_string);
+ else if (!expect)
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting failed because (%s) was NULL\n",
+ file, line, expect_string);
+ if (!have || !expect)
+ exit(1);
+
+ switch (how) {
+ STR_CASE(ASSERT_EQ, !strcmp);
+ STR_CASE(ASSERT_NE, strcmp);
+ STR_CASE(ASSERT_IS_IN, strstr);
+ STR_CASE(ASSERT_NOT_IN, !strstr);
+ SWAP_STR_CASE(ASSERT_CONTAINS_ALL, ASSERT_IS_IN);
+ SWAP_STR_CASE(ASSERT_CONTAINS_NOT_ALL, ASSERT_NOT_INT);
+ SWAP_STR_CASE(ASSERT_LE, ASSERT_GE);
+ SWAP_STR_CASE(ASSERT_LT, ASSERT_GT);
+ SWAP_STR_CASE(ASSERT_NOT_LE, ASSERT_NOT_GE);
+ SWAP_STR_CASE(ASSERT_NOT_LT, ASSERT_NOT_GT);
+ default:
+ have_len = strlen(have);
+ expect_len = strlen(expect);
+ if (rev)
+ goto assert_end;
+ else
+ goto assert_start;
+ }
+ fprintf(stderr, "TEST FAILED at %s:%i: asserting %s(%s, %s), actualised as %s(%s, %s)\n",
+ file, line, function, have_string, expect_string,
+ function, quote(have, &have_quote), quote(expect, &expect_quote));
+free_and_exit:
+ free(have_quote);
+ free(expect_quote);
+ exit(1);
+
+assert_start:
+ switch (how) {
+ case ASSERT_GE:
+ if (have_len >= expect_len && !strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) >= strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu >= %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_NOT_GE:
+ if (have_len < expect_len || strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) < strlen(%s) || strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu < %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_GT:
+ if (have_len > expect_len && !strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) > strlen(%s) && !strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu > %zu && !strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ case ASSERT_NOT_GT:
+ if (have_len <= expect_len || strncmp(have, expect, expect_len))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) <= strlen(%s) || strncmp(%s, %s, strlen(%s)), "
+ "actualised as %zu <= %zu || strncmp(%s, %s, %zu)\n",
+ file, line, have_string, expect_string, have_string, expect_string, expect_string,
+ have_len, expect_len, quote(have, &have_quote), quote(expect, &expect_quote), expect_len);
+ break;
+ default:
+ abort();
+ }
+ goto free_and_exit;
+
+assert_end:
+ switch (how) {
+ case ASSERT_GE:
+ if (have_len >= expect_len && !strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) >= strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu >= %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_NOT_GE:
+ if (have_len > expect_len || strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) < strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu < %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_GT:
+ if (have_len > expect_len && !strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) > strlen(%s) && !strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu > %zu && !strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ case ASSERT_NOT_GT:
+ if (have_len <= expect_len || strcmp(&have[have_len - expect_len], expect))
+ return;
+ fprintf(stderr, "TEST FAILED at %s:%i: "
+ "asserting strlen(%s) <= strlen(%s) || strcmp(&(%s)[strlen(%s) - strlen(%s)], %s), "
+ "actualised as %zu <= %zu || strcmp(&%s[%ti], %s)\n",
+ file, line, have_string, expect_string, have_string, have_string, expect_string,
+ expect_string, have_len, expect_len, quote(have, &have_quote),
+ (ptrdiff_t)have_len - (ptrdiff_t)expect_len, quote(expect, &expect_quote));
+ break;
+ default:
+ abort();
+ }
+ goto free_and_exit;
+}
+
+
+void
+test_assert(const char *file, int line, enum assert_type type, enum assert_how how,
+ const char *have_string, const char *expect_string, const void *have, const void *expect)
+{
+ switch (type) {
+ case ASSERT_ENUM:
+ case ASSERT_UINT:
+ test_assert_uint(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_INT:
+ test_assert_int(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_NULL:
+ case ASSERT_PTR:
+ test_assert_ptr(file, line, how, have_string, expect_string, have, expect);
+ break;
+
+ case ASSERT_STR:
+ test_assert_str(file, line, how, have_string, expect_string, have, expect, 0);
+ break;
+
+ case ASSERT_STR_REV:
+ test_assert_str(file, line, how, have_string, expect_string, have, expect, 1);
+ break;
+
+ default:
+ abort();
+ }
+}