diff --git a/.gitignore b/.gitignore index 8419b131e..48d733624 100644 --- a/.gitignore +++ b/.gitignore @@ -15,8 +15,12 @@ aclocal.m4 autom4te.cache/ config.status configure +conftest.* .libs +# since we use subdir-objects as automake option +.dirstamp + # ignore "libtoolized" m4 files, but keep our (custom-prefixed) ones /m4/* !/m4/ax_*.m4 @@ -25,6 +29,9 @@ configure /.tarball-version /tag-* +/lib/qblog_script.ld +# already captured with wildcard: /lib/qblog_script.la + libtool .version libqb.spec @@ -35,4 +42,12 @@ abi_dumps TAGS *~ test-driver -tests/*.trs + +/tests/**/*.real +/tests/**/*.trs +/tests/functional/log_callsite_bench.c +/tests/functional/**/log_callsite_bench_section*.c +/tests/functional/**/log_client +/tests/functional/**/log_interlib_client +/tests/functional/_pkgs +/tests/functional/_results diff --git a/.travis.yml b/.travis.yml index d9faed9b6..5ee9a1052 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,9 @@ addons: script: RPMBUILDOPTS_="--nodeps --define '_without_check 1'"; ./autogen.sh && ./configure - && make distcheck CPPFLAGS=-Dci_dump_shm_usage VERBOSE=1 + && make -C lib V=1 + && make -C tests/functional/log_internal V=1 check + && make V=1 CPPFLAGS=-Dci_dump_shm_usage distcheck && sed "s|RPMBUILDOPTS =|\\0 ${RPMBUILDOPTS_}|" Makefile | make -f- rpm after_failure: diff --git a/check b/check index 6a2f29d40..79062c570 100755 --- a/check +++ b/check @@ -90,6 +90,8 @@ check_nosection() { echo "checking nosection" echo "=======================" # no __attribute__((section)) + # NOTE: alternatively CPPFLAGS=-DQB_KILL_ATTRIBUTE_SECTION, but + # that won't shortcut many initial/configure decisions check "ac_cv_link_attribute_section=no" } diff --git a/configure.ac b/configure.ac index 7ec8f0eb9..540712b7e 100644 --- a/configure.ac +++ b/configure.ac @@ -13,7 +13,7 @@ AC_CONFIG_SRCDIR([lib/ringbuffer.c]) AC_CONFIG_HEADERS([include/config.h include/qb/qbconfig.h]) AC_USE_SYSTEM_EXTENSIONS -AM_INIT_AUTOMAKE([-Wno-portability dist-xz]) +AM_INIT_AUTOMAKE([-Wno-portability dist-xz subdir-objects]) dnl automake >= 1.11 offers --enable-silent-rules for suppressing the output from dnl normal compilation. When a failure occurs, it will then display the full dnl command line @@ -77,6 +77,10 @@ AC_CHECK_PROGS([DOXYGEN], [doxygen]) AM_CONDITIONAL(HAVE_DOXYGEN, test -n "${DOXYGEN}") AC_CHECK_PROGS([SPLINT], [splint]) AM_CONDITIONAL(HAVE_SPLINT, test -n "${SPLINT}") +AC_CHECK_TOOLS([NM], [eu-nm nm], [:]) +AC_CHECK_TOOLS([READELF], [eu-readelf readelf], [:]) +AM_PATH_PYTHON([2.6],, [:]) +AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != :]) ## local helper functions @@ -501,6 +505,14 @@ AC_ARG_ENABLE([debug], AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage],[coverage analysis of the codebase])]) +AC_ARG_ENABLE([interlib-deps], + [AS_HELP_STRING([--disable-interlib-deps], + [disable inter-library dependencies (might break builds)])]) + +AC_ARG_ENABLE([nosection-fallback], + [AS_HELP_STRING([--enable-nosection-fallback], + [allow (logging compat-breaking?) fallback when orphan section dead-ended])]) + AC_ARG_ENABLE([slow-tests], [AS_HELP_STRING([--enable-slow-tests],[build and run slow tests])]) @@ -609,6 +621,19 @@ else COVERAGE_LDFLAGS="" fi +# --- inter-library dependencies --- +# because of debian/ubuntu swimming against the stream +# https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=702737, +# override the libtool variable by force because the current +# arrangement relies on transitive dependency propagation +AC_MSG_NOTICE([Enabling inter-library dependencies: $enable_interlib_deps]) +if test "x${enable_interlib_deps}" = xno; then + link_all_deplibs=no +else + link_all_deplibs=yes +fi + +# --- slow tests --- if test "x${enable_slow_tests}" = xyes ; then AC_DEFINE([HAVE_SLOW_TESTS], 1,[have slow tests]) AC_MSG_NOTICE([Enabling Slow tests]) @@ -618,28 +643,141 @@ AC_SUBST(HAVE_SLOW_TESTS) # --- callsite sections --- if test "x${GCC}" = xyes; then + AX_SAVE_FLAGS AC_MSG_CHECKING([whether GCC supports __attribute__((section()) + ld supports orphan sections]) if test "x${ac_cv_link_attribute_section}" = x ; then - AC_TRY_LINK([#include - extern void * __start___verbose, * __stop___verbose;], - [static int my_var __attribute__((section("__verbose"))) = 5; - if (__start___verbose == __stop___verbose) assert(0); - if (my_var == 5) return 0; - else return -1; - ], - [gcc_has_attribute_section=yes], - [gcc_has_attribute_section=no]) + LDFLAGS="${LDFLAGS_save} -shared -fPIC" # we are compiling shared lib + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [[#include + extern int __start___verbose[], __stop___verbose[]; + int test(void) { + static int my_var __attribute__((section("__verbose"))) = 3; + assert("L:non-empty data section" + && __start___verbose != __stop___verbose); + assert("L:no data section value loss" + && my_var == 3 /* for 2.29.1+ */); + return *((int *) __start___verbose); }]] + )], + [gcc_has_attribute_section=yes; cp "conftest${ac_exeext}" "conftest.so"], + [gcc_has_attribute_section=no] + ) + AX_RESTORE_FLAGS else gcc_has_attribute_section=${ac_cv_link_attribute_section} fi - AC_MSG_RESULT($gcc_has_attribute_section) - if test $gcc_has_attribute_section = yes; then - AC_DEFINE([QB_HAVE_ATTRIBUTE_SECTION], 1, - [Enabling code using __attribute__((section))]) - PACKAGE_FEATURES="$PACKAGE_FEATURES attribute-section" + + # in the failing case (e.g. with ld from binutils 2.29), it's likely the + # following will fail readily in linkage (hidden symbol `__stop___verbose' + # in conftest is referenced by DSO), but keep the sensible test + # (in-executable symbol is expected to be propagated into the library, + # and to draw the full circle back to the executable through standard + # return value passing (respectively no-exec probing to spot the issue); + # -rpath passed because LD_LIBRARY_PATH exporting is unwieldy here); + # moreover, "my_var" == 3 assertion above (respectively checking if the + # boundary symbol visibility differs from DEFAULT in readelf output) is + # necessary so that binutils 2.29.1+ will not slip other parts of the + # overall is-workaround-needed harness, as it restored some (but not + # all) of the original behaviour, but the workaround is still provably + # needed + if test "x${gcc_has_attribute_section}" = xyes; then + AC_MSG_CHECKING([whether linker emits global boundary symbols for orphan sections]) + LIBS="${LIBS} -L. -l:conftest${shrext_cmds} -Wl,-rpath=$(pwd)" + dnl could be turned to AC_TRY_RUN (first assertion is equivalent to + dnl the further check in action-if-true), but that would prevent + dnl cross-building + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + extern int __start___verbose[], __stop___verbose[]; + int test(void);]], + [[static int my_var __attribute__((section("__verbose"))) = 5; + assert("E:non-empty data section" + && __start___verbose != __stop___verbose); + assert("E:no data section value loss" + && my_var == test() /*5?*/);]] + )], + [# alternatively something like (but requires number parsing): + # readelf -SW "conftest${ac_exeext}" \ + # | sed -n '/__verbose/s/^\s*//p' | tr -s ' ' | cut -d" " -f6 + verbose_start_addr=$(${NM} -g --portability -- "conftest${ac_exeext}" \ + | grep __start___verbose | cut -d" " -f 3) + verbose_stop_addr=$(${NM} -g --portability -- "conftest${ac_exeext}" \ + | grep __stop___verbose | cut -d" " -f 3) + test "${verbose_start_addr}" = "${verbose_stop_addr}" \ + && gcc_has_attribute_section_visible=no \ + || { verbose_start_type=$(${READELF} -s backup \ + | sed -n '/__start___verbose/{s/^\s*//p;q}' \ + | tr -s ' ' \ + | cut -d" " -f6) + test "${verbose_start_type}" = DEFAULT \ + && gcc_has_attribute_section_visible=yes \ + || gcc_has_attribute_section_visible=no; }], + [gcc_has_attribute_section_visible=no] + ) + AX_RESTORE_FLAGS + AC_MSG_RESULT($gcc_has_attribute_section_visible) + rm -f "conftest${shrext_cmds}" + + if test "x${gcc_has_attribute_section_visible}" = xno; then + # check if the linker script based workaround is + # feasible at all, otherwise fallback to using no + # section attribute while making some noise about it + # as combining with-without accustomed logging + # participants is currently uncharted waters + AC_MSG_CHECKING([whether linker workaround for orphan sections usable]) + >conftest.ld cat <<-EOF + SECTIONS { + __verbose : { + __start___verbose = .; + *(__verbose); + __stop___verbose = .; + } + } + EOF + LDFLAGS="${LDFLAGS} -Wl,conftest.ld" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM( + [[#include + extern int __start___verbose[], __stop___verbose[]; + int test(void);]], + [[static int my_var __attribute__((section("__verbose"))) = 5; + assert("E:non-empty data section" + && __start___verbose != __stop___verbose); + assert("E:no data section value loss" + && my_var == 5);]] + )], + [], + [gcc_has_attribute_section=no] + ) + AX_RESTORE_FLAGS + AC_MSG_RESULT([$gcc_has_attribute_section]) + rm -f "conftest.ld" + fi + + if test "x${gcc_has_attribute_section}" = xyes; then + AC_DEFINE([QB_HAVE_ATTRIBUTE_SECTION], 1, + [Enabling code using __attribute__((section))]) + AC_SUBST([client_dlopen_LIBS],[$dlopen_LIBS]) + if test "x${gcc_has_attribute_section_visible}" = xyes; then + PACKAGE_FEATURES="$PACKAGE_FEATURES attribute-section" + else + AC_DEFINE([QB_NEED_ATTRIBUTE_SECTION_WORKAROUND], 1, + [Enabling code using __attribute__((section))]) + PACKAGE_FEATURES="$PACKAGE_FEATURES attribute-section-workaround" + fi + elif test "x${enable_nosection_fallback}" = xyes; then + AC_MSG_NOTICE([Falling back to not using orphan section]) + else + AC_MSG_ERROR([Would use section attribute, cannot; see --enable-nosection-fallback]) + fi fi fi +AM_CONDITIONAL(HAVE_GCC_ATTRIBUTE_SECTION, [test "x${gcc_has_attribute_section}" = xyes]) +AM_CONDITIONAL(NEED_GCC_ATTRIBUTE_SECTION_WORKAROUND, + [test "x${gcc_has_attribute_section}" = xyes \ + && test "x${gcc_has_attribute_section_visible}" != xyes]) # --- ansi --- if test "x${enable_ansi}" = xyes && \ @@ -713,12 +851,19 @@ AC_CONFIG_FILES([Makefile lib/libqb.pc tools/Makefile tests/Makefile + tests/functional/Makefile + tests/functional/log_external/Makefile + tests/functional/log_internal/Makefile tests/test.conf examples/Makefile docs/Makefile docs/common.dox docs/html.dox - docs/man.dox]) + docs/man.dox + lib/qblog_script.la:lib/qblog_script.la.in]) + +AC_CONFIG_LINKS([lib/qblog_script_noop.ld:lib/qblog_script_noop.ld + tests/functional/GNUmakefile:tests/functional/GNUmakefile]) AC_OUTPUT diff --git a/include/qb/qbconfig.h.in b/include/qb/qbconfig.h.in index c1852e12e..9094dc7d9 100644 --- a/include/qb/qbconfig.h.in +++ b/include/qb/qbconfig.h.in @@ -30,7 +30,12 @@ /* Enabling code using __attribute__((section)) */ #undef QB_HAVE_ATTRIBUTE_SECTION -/* versioning info: MAJOR, MINOR, MICRO, and REST components */ +/* versioning info: MAJOR, MINOR, MICRO, and REST components; + note that static compile-time info is not that useful as consulting + the respectively named members of qb_version struct constant under + @c qb_ver identifier (or @c qb_ver_str equivalent of the local + upper-cased value) directly from libqb in run-time (see qbutil.h), + but that was only introduced after v1.0.2 */ #undef QB_VER_MAJOR #undef QB_VER_MINOR #undef QB_VER_MICRO diff --git a/include/qb/qblog.h b/include/qb/qblog.h index 3cb4eef86..1cb63194d 100644 --- a/include/qb/qblog.h +++ b/include/qb/qblog.h @@ -1,9 +1,10 @@ /* - * Copyright (C) 2010 Red Hat, Inc. + * Copyright 2017 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld + * Jan Pokorny * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by @@ -38,9 +39,14 @@ extern "C" { #include #include -#ifdef S_SPLINT_S +#if defined(QB_KILL_ATTRIBUTE_SECTION) || defined(S_SPLINT_S) #undef QB_HAVE_ATTRIBUTE_SECTION -#endif /* S_SPLINT_S */ +#endif /* defined(QB_KILL_ATTRIBUTE_SECTION) || defined(S_SPLINT_S) */ + +#ifdef QB_HAVE_ATTRIBUTE_SECTION +#include /* possibly needed for QB_LOG_INIT_DATA */ +#include /* dynamic linking: dlopen, dlsym, dladdr, ... */ +#endif /** * @file qblog.h @@ -64,6 +70,20 @@ extern "C" { * } * @endcode * + * @note + * In practice, such a minimalistic approach hardly caters real use cases. + * Following section discusses the customization. Moreover when employing + * the log module is bound to its active use (some log messages are assuredly + * emitted within the target compilation unit), it's quite vital to instrument + * the target side with @c QB_LOG_INIT_DATA() macro placed in the top file + * scope in exactly one source file (preferably the main one) to be mixed into + * the resulting compilation unit. This is a self-defensive measure for when + * the linker-assisted collection of callsite data silently fails, which could + * otherwise go unnoticed, causing troubles down the road, but alas it cannot + * discern misuse of @c QB_LOG_INIT_DATA() macro in no-logging context from + * broken callsite section handling assumptions owing to overboard fancy + * linker -- situation that the self-check aims to detect in the first place. + * * @par Configuring log targets. * A log target can be syslog, stderr, the blackbox, stdout, or a text file. * By default only syslog is enabled. @@ -268,15 +288,104 @@ typedef void (*qb_log_filter_fn)(struct qb_log_callsite * cs); extern struct qb_log_callsite QB_ATTR_SECTION_START[]; extern struct qb_log_callsite QB_ATTR_SECTION_STOP[]; -/* mere linker sanity check, possible future extension for internal purposes */ -#define QB_LOG_INIT_DATA(name) \ - void name(void); \ - void name(void) \ - { if (QB_ATTR_SECTION_START == QB_ATTR_SECTION_STOP) assert(0); } \ +/* Related to the next macro that is -- unlike this one -- a public API */ +#ifndef _GNU_SOURCE +#define QB_NONAPI_LOG_INIT_DATA_EXTRA_(name) \ + _Pragma(QB_PP_STRINGIFY(GCC warning QB_PP_STRINGIFY( \ + without "_GNU_SOURCE" defined (directly or not) \ + QB_LOG_INIT_DATA cannot check sanity of libqb proper \ + nor of the target site originating this check alone))) +#else +#define QB_NONAPI_LOG_INIT_DATA_EXTRA_(name) \ + { Dl_info work_dli; \ + /* libqb sanity (locating libqb by it's relatively unique \ + non-functional symbols -- the two are mutually exclusive, the \ + ordinarily latter was introduced by accident, the former is \ + intentional -- due to possible confusion otherwise) */ \ + if ((dladdr(dlsym(RTLD_DEFAULT, "qb_ver_str"), &work_dli) \ + || dladdr(dlsym(RTLD_DEFAULT, "facilitynames"), &work_dli)) \ + && (work_handle = dlopen(work_dli.dli_fname, \ + RTLD_LOCAL|RTLD_LAZY)) != NULL) { \ + work_s1 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_START_STR); \ + work_s2 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_STOP_STR); \ + assert("libqb's callsite section is observable, otherwise \ +libqb's build is at fault, preventing reliable logging" \ + && work_s1 != NULL && work_s2 != NULL); \ + assert("libqb's callsite section is populated, otherwise \ +libqb's build is at fault, preventing reliable logging" \ + && work_s1 != work_s2); \ + dlclose(work_handle); } \ + /* sanity of the target site originating this check alone */ \ + if (dladdr(dlsym(RTLD_DEFAULT, QB_PP_STRINGIFY(name)), &work_dli) \ + && (work_handle = dlopen(work_dli.dli_fname, \ + RTLD_LOCAL|RTLD_LAZY)) != NULL) { \ + work_s1 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_START_STR); \ + work_s2 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_STOP_STR); \ + assert("target's own callsite section observable, otherwise \ +target's own linkage at fault and logging would not work reliably \ +(unless QB_LOG_INIT_DATA macro used unexpectedly in no-logging context)"\ + && work_s1 != NULL && work_s2 != NULL); \ + assert("target's own callsite section non-empty, otherwise \ +target's own linkage at fault and logging would not work reliably \ +(unless QB_LOG_INIT_DATA macro used unexpectedly in no-logging context)"\ + && work_s1 != work_s2); \ + dlclose(work_handle); } } +#endif /* _GNU_SOURCE */ + +/** + * Optional on-demand self-check of 1/ toolchain sanity (prerequisite for + * the logging subsystem to work properly) and 2/ non-void active use of + * logging (satisfied with a justifying existence of a logging callsite as + * defined with a @c qb_logt invocation) at the target (but see below), which + * is supposedly assured by it's author(!) as of relying on this very macro + * [technically, the symbols that happen to be resolved under the respective + * identifiers do not necessarily originate in the same compilation unit as + * when it's not the end executable (or by induction, a library positioned + * earlier in the symbol lookup order) but a shared library, the former takes + * a precedence unless that site comes short of exercising the logging, + * making its callsite section empty and, in turn, without such boundary + * symbols, hence making the resolution continue further in the lookup order + * -- despite fuzzily targeted attestation, the check remains reasonable]; + * only effective when link-time ("run-time amortizing") callsite collection + * is; as a side effect, it can ensure the boundary-denoting symbols for the + * target collection area are kept alive with some otherwise unkind linkers. + * + * Applying this macro in the target program/library is strongly recommended + * whenever the logging as framed by this header file is in use. + * Moreover, it's important to state that using this check while not ensuring + * @c _GNU_SOURCE macro definition is present at compile-time means only half + * of the available sanity checking will be performed, possibly resulting + * in libqb's own internally logged messages being lost without warning. + */ +#define QB_LOG_INIT_DATA(name) \ + void name(void); \ + void name(void) { \ + void *work_handle; struct qb_log_callsite *work_s1, *work_s2; \ + /* our own (target's) sanity, or possibly that of higher priority \ + symbol resolution site (unless target equals end executable) \ + or even the lower one if no such predecessor defines these */ \ + if ((work_handle = dlopen(NULL, RTLD_LOCAL|RTLD_LAZY)) != NULL) { \ + work_s1 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_START_STR); \ + work_s2 = (struct qb_log_callsite *) \ + dlsym(work_handle, QB_ATTR_SECTION_STOP_STR); \ + assert("implicit callsite section is observable, otherwise \ +target's and/or libqb's build is at fault, preventing reliable logging" \ + && work_s1 != NULL && work_s2 != NULL); \ + dlclose(work_handle); /* perhaps overly eager thing to do */ } \ + QB_NONAPI_LOG_INIT_DATA_EXTRA_(name); \ + /* finally, original, straightforward check */ \ + assert("implicit callsite section is populated, otherwise \ +target's build is at fault, preventing reliable logging" \ + && QB_ATTR_SECTION_START != QB_ATTR_SECTION_STOP); } \ void __attribute__ ((constructor)) name(void); #else #define QB_LOG_INIT_DATA(name) -#endif +#endif /* QB_HAVE_ATTRIBUTE_SECTION */ /** * Internal function: use qb_log() or qb_logt() diff --git a/include/qb/qbutil.h b/include/qb/qbutil.h index bfce349fc..b02ce8db7 100644 --- a/include/qb/qbutil.h +++ b/include/qb/qbutil.h @@ -289,6 +289,17 @@ uint64_t qb_util_stopwatch_time_split_get(qb_util_stopwatch_t *sw, uint32_t receint, uint32_t older); +/** Structured library versioning info */ +extern const struct qb_version { + uint8_t major; /**< Major component */ + uint8_t minor; /**< Minor component */ + uint8_t micro; /**< Micro component */ + const char *rest; /**< Rest (pertaining the mid-release-point) */ +} qb_ver; + +/** Complete library versioning info as a string */ +extern const char *const qb_ver_str; + /* *INDENT-OFF* */ #ifdef __cplusplus } diff --git a/lib/Makefile.am b/lib/Makefile.am index 0bebeb5e7..6ca6b1518 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -19,6 +19,7 @@ # along with libqb. If not, see . +CLEANFILES = qblog_script.ld MAINTAINERCLEANFILES = Makefile.in noinst_HEADERS = ipc_int.h util_int.h ringbuffer_int.h loop_int.h \ @@ -29,7 +30,7 @@ AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include lib_LTLIBRARIES = libqb.la -libqb_la_LDFLAGS = -version-info 18:2:18 +libqb_la_LDFLAGS = -version-info 19:0:19 source_to_lint = util.c hdb.c ringbuffer.c ringbuffer_helper.c \ array.c loop.c loop_poll.c loop_job.c \ @@ -39,9 +40,48 @@ source_to_lint = util.c hdb.c ringbuffer.c ringbuffer_helper.c \ log_syslog.c log_dcs.c log_format.c \ map.c skiplist.c hashtable.c trie.c +# Following two files related to linkage using classic ld from binutils 2.29+ +# with which we cannot afford to lose public access to section boundary symbols +# (as the mentioned version started to scope them privately by default, see +# the comment within the first of the files, ultimately leading to broken +# logging functionality of libqb) deserve a bit of explanation: +# * qblog_script.ld +# - linker script that instructs the output section that those symbols should +# be visible, i.e. supports the same behaviour regardless of ld version +# - serves two purposes: +# . local: libqb itself and its "private" (cf. examples) users need those +# symbols visible, which is achieved with a help of the other file +# . system-wide: whenever the non-private library users link against libqb +# (it's development files), this linker script with +# prepended INPUT command so as to refer to the actual +# libqb library (it's numbered alias that is eventually +# resolved to proper shared library) is masked as libqb.so, +# this arrangement achieves the libqb's user will have +# the discussed symbols visible alike +# * qblog_script.la +# - as mentioned earlier, this indirectly hooks into libtool machinery, with +# the only true intention of injecting "-Wl," +# into "inherited_linker_flags" libtool archive variable, from where it's +# subsequently spread into the build process of all the internal library +# users, assuming they have their dep arranged as "user_LIBADD=libqb.la" +# (this also alleviates the burden on getting things right if, e.g., any +# libqb user consumes it directly like this from its own sub-checkout tree) +# - it indirectly, once libtool prechew the original link command +# originally referring to this file, it turns such reference into the +# "real" library reference (here combining libdir and old_library +# variables within the file), also ensures libqb itself will visibly +# expose the discussed symbols, because such references point again to +# the (not enriched) linker script file that will get interpreted just +# like that during the last build step of the library +EXTRA_libqb_la_DEPENDENCIES = qblog_script.ld qblog_script.la +EXTRA_DIST = qblog_script.ld.in qblog_script.la.in qblog_script_noop.ld + libqb_la_SOURCES = $(source_to_lint) unix.c libqb_la_CFLAGS = $(PTHREAD_CFLAGS) libqb_la_LIBADD = $(LTLIBOBJS) $(dlopen_LIBS) $(PTHREAD_LIBS) $(socket_LIBS) +if NEED_GCC_ATTRIBUTE_SECTION_WORKAROUND +libqb_la_LIBADD += qblog_script.la +endif AM_LDFLAGS = $(LDFLAGS_COPY:-Bsymbolic-functions=) @@ -60,9 +100,42 @@ else endif endif - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libqb.pc +qblog_script.ld: %.ld: %.ld.in + $(AM_V_GEN)$(CPP) -xc -I$(top_srcdir)/include -D_GNU_SOURCE -C -P $< \ + | sed -n "/$$(sed -n '/^[^#]/{s/[*\/]/\\&/g;p;q;}' $<)/,$$ p" \ + > $@ + +# sadly, there's a distinction between "exec" and "data" install, and it's hard +# to decouple install-exec-hook below (.pc file is platform-dependent, anyway) +pkgconfigexecdir = $(libdir)/pkgconfig +pkgconfigexec_DATA = libqb.pc + +# find the libqb.so symlink's target, if so, try to find out, iteratively, +# its gradually shorter forms that likewise symlinks the same target as the +# original libqb.so path, point to that file from the linker script using +# qblog_script.ld as a template, storing result in place of original libqb.so +# (e.g., libqb.so := "INPUT(libqb.so.0) " [...] "SECTIONS { " [...] "}") +# NOTE: readlink nor realpath are POSIX; not chained links ready +# NOTE: conservative check, i.e., not per NEED_GCC_ATTRIBUTE_SECTION_WORKAROUND +if HAVE_GCC_ATTRIBUTE_SECTION +install-exec-hook: qblog_script.ld + target=$$(ls -l "$(DESTDIR)$(libdir)/libqb.so" || :); \ + target=$${target#* -> }; t1_bn=$$(basename "$${target}" || :); \ + while test -n "$${t1_bn}"; do t2_bn=$${t1_bn%.*[0-9]*}; \ + test "$${t2_bn}" != libqb.so || break; \ + test -L "$${t2_bn}" || { t1_bn=$${t2_bn}; continue; }; \ + t2_target=$$(ls -l "$${t2_bn}" || break); t2_target=$${t2_target#* -> }; \ + test "$${t2_target}" = "$${target}" || break; \ + t1_bn=$${t2_bn}; done; test -n "$${t1_bn}" || \ + { echo "only applicable to SO symlink scheme"; exit 1; }; \ + echo "$${t1_bn}" > "$(DESTDIR)$(libdir)/libqb.so-t" + so_ver=$$(cat "$(DESTDIR)$(libdir)/libqb.so-t"); \ + echo "INPUT($${so_ver})" > "$(DESTDIR)$(libdir)/libqb.so-t"; \ + cat $< >> "$(DESTDIR)$(libdir)/libqb.so-t"; \ + sed -i -- "s/libqb.so./$${so_ver}/" \ + "$(DESTDIR)$(libdir)/libqb.so-t" "$(DESTDIR)$(pkgconfigexecdir)/libqb.pc" + mv -f "$(DESTDIR)$(libdir)/libqb.so-t" "$(DESTDIR)$(libdir)/libqb.so" +endif if HAVE_SPLINT check_SCRIPTS = run_splint.sh diff --git a/lib/libqb.pc.in b/lib/libqb.pc.in index 8a8d0ba8f..d9839bf41 100644 --- a/lib/libqb.pc.in +++ b/lib/libqb.pc.in @@ -7,5 +7,17 @@ Name: libqb Version: @PACKAGE_VERSION@ Description: libqb Requires: -Libs: -L${libdir} -lqb @LIBS@ +Libs: -L${libdir} -lqb @client_dlopen_LIBS@ +# NOTE: If -lqb not usable for linking (e.g. linker not compatible with +# linker scripts ad-hoc modifying output sections), try recent +# ld.bfd/binutils linker first when available, otherwise you can +# try "-l:libqb.so." link switch that bypasses said linker +# script -- but beware, logging may be less efficient and may lack +# possible future optimizations and extra features. Consequently, +# logging issues (typically bound to QB_LOG_INIT_DATA macro) can be +# mitigated with QB_KILL_ATTRIBUTE_SECTION macro defined for a build. +# NOTE: when concerned about a warning coming from the build process like +# warning: [...]libqb.so contains output sections; did you forget -T? +# and the build finishes OK, take it merely as a harmless side-effect +Libs.private: @LIBS@ Cflags: -I${includedir} diff --git a/lib/log.c b/lib/log.c index bfd218fac..7cff29854 100644 --- a/lib/log.c +++ b/lib/log.c @@ -40,6 +40,13 @@ #include "util_int.h" #include +#if defined(QB_NEED_ATTRIBUTE_SECTION_WORKAROUND) && !defined(S_SPLINT_S) +/* following only needed to force these symbols be global + with ld 2.29: https://bugzilla.redhat.com/1477354 */ +struct qb_log_callsite __attribute__((weak)) QB_ATTR_SECTION_START[] = { 0 }; +struct qb_log_callsite __attribute__((weak)) QB_ATTR_SECTION_STOP[] = { 0 }; +#endif + static struct qb_log_target conf[QB_LOG_TARGET_MAX]; static uint32_t conf_active_max = 0; static int32_t in_logger = QB_FALSE; @@ -853,6 +860,18 @@ qb_log_init(const char *name, int32_t facility, uint8_t priority) { int32_t l; enum qb_log_target_slot i; +#ifdef QB_HAVE_ATTRIBUTE_SECTION + void *work_handle; struct qb_log_callsite *work_s1, *work_s2; + Dl_info work_dli; +#endif /* QB_HAVE_ATTRIBUTE_SECTION */ + /* cannot reuse single qb_log invocation in various contexts + through the variables (when section attribute in use), + hence this indirection */ + enum { + preinit_err_none, + preinit_err_target_sec, + preinit_err_target_empty, + } preinit_err = preinit_err_none; l = pthread_rwlock_init(&_listlock, NULL); assert(l == 0); @@ -871,6 +890,28 @@ qb_log_init(const char *name, int32_t facility, uint8_t priority) qb_log_dcs_init(); #ifdef QB_HAVE_ATTRIBUTE_SECTION + /* sanity check that target chain supplied QB_ATTR_SECTION_ST{ART,OP} + symbols and hence the local references to them are not referencing + the proper libqb's ones (locating libqb by it's relatively unique + non-functional symbols -- the two are mutually exclusive, the + ordinarily latter was introduced by accident, the former is + intentional -- due to possible confusion otherwise) */ + if ((dladdr(dlsym(RTLD_DEFAULT, "qb_ver_str"), &work_dli) + || dladdr(dlsym(RTLD_DEFAULT, "facilitynames"), &work_dli)) + && (work_handle = dlopen(work_dli.dli_fname, + RTLD_LOCAL|RTLD_LAZY)) != NULL) { + work_s1 = (struct qb_log_callsite *) + dlsym(work_handle, QB_ATTR_SECTION_START_STR); + work_s2 = (struct qb_log_callsite *) + dlsym(work_handle, QB_ATTR_SECTION_STOP_STR); + if (work_s1 == QB_ATTR_SECTION_START + || work_s2 == QB_ATTR_SECTION_STOP) { + preinit_err = preinit_err_target_sec; + } else if (work_s1 == work_s2) { + preinit_err = preinit_err_target_empty; + } + dlclose(work_handle); /* perhaps overly eager thing to do */ + } qb_log_callsites_register(QB_ATTR_SECTION_START, QB_ATTR_SECTION_STOP); dl_iterate_phdr(_log_so_walk_callback, NULL); _log_so_walk_dlnames(); @@ -884,6 +925,25 @@ qb_log_init(const char *name, int32_t facility, uint8_t priority) _log_target_state_set(&conf[QB_LOG_SYSLOG], QB_LOG_STATE_ENABLED); (void)qb_log_filter_ctl(QB_LOG_SYSLOG, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", priority); + + if (preinit_err == preinit_err_target_sec) + qb_util_log(LOG_NOTICE, "(libqb) log module hasn't observed" + " target chain supplied callsite" + " section, target's and/or libqb's" + " build is at fault, preventing" + " reliable logging (unless qb_log_init" + " invoked in no-custom-logging context" + " unexpectedly, or target chain built" + " purposefully without these sections)"); + else if (preinit_err == preinit_err_target_empty) { + qb_util_log(LOG_WARNING, "(libqb) log module has observed" + " target chain supplied section" + " unpopulated, target's and/or libqb's" + " build is at fault, preventing" + " reliable logging (unless qb_log_init" + " invoked in no-custom-logging context" + " unexpectedly)"); + } } void diff --git a/lib/log_format.c b/lib/log_format.c index 712f44756..e7e1f40bf 100644 --- a/lib/log_format.c +++ b/lib/log_format.c @@ -49,7 +49,7 @@ static struct syslog_names prioritynames[] = { {NULL, -1} }; -struct syslog_names facilitynames[] = { +static struct syslog_names facilitynames[] = { {"auth", LOG_AUTH}, #if defined(LOG_AUTHPRIV) {"authpriv", LOG_AUTHPRIV}, diff --git a/lib/qblog_script.la.in b/lib/qblog_script.la.in new file mode 100644 index 000000000..f262df8cf --- /dev/null +++ b/lib/qblog_script.la.in @@ -0,0 +1,15 @@ +# Generated by libtool +# NOTE: above line is just to pass func_ltwrapper_script_p sanity check of +# libtool script, as we are basically sort of abusing it so as to inject +# our custom linker script to "private" (cf. examples) users of libqb.la + +# shall rather carry a location of old_library (possibly libdir or something +# else, but installed=no needed to suppress 'library moved' warning then) as +# it's together (with libtool implied prefix otherwise) used for linking libqb +libdir=@abs_builddir@ + +# avoids issues with library_names (spurious rpath emitting, relink-on-install) +old_library=qblog_script_noop.ld + +# subject of our injection into libqb.la impacting build time for private users +inherited_linker_flags=-Wl,@abs_builddir@/qblog_script.ld diff --git a/lib/qblog_script.ld.in b/lib/qblog_script.ld.in new file mode 100644 index 000000000..a188ac233 --- /dev/null +++ b/lib/qblog_script.ld.in @@ -0,0 +1,30 @@ +#include +/* GNU ld script + This atypical arrangement enforces global visibility of boundary symbols + (QB_ATTR_SECTION_START, QB_ATTR_SECTION_STOP) for the custom section + QB_ATTR_SECTION used for compile-time offloading of the logging call sites + tracking. While libqb relies on these being global, default linker from + binutils change the visibility as of version 2.29, making the logging + unusable without artificial stimulus: https://bugzilla.redhat.com/1477354 + + NOTE: If -lqb not usable for linking (e.g. linker not compatible with + linker scripts ad-hoc modifying output sections), try recent + ld.bfd/binutils linker first when available, otherwise you can + try "-l:libqb.so." link switch that bypasses said linker + script -- but beware, logging may be less efficient and may lack + possible future optimizations and extra features. Consequently, + logging issues (typically bound to QB_LOG_INIT_DATA macro) can be + mitigated with QB_KILL_ATTRIBUTE_SECTION macro defined for a build. + NOTE: When concerned about a warning coming from the build process like + warning: [...]libqb.so contains output sections; did you forget -T? + while it finishes OK, consider it merely a harmless side-effect. + */ +SECTIONS { +#ifdef QB_HAVE_ATTRIBUTE_SECTION + QB_ATTR_SECTION : { + QB_ATTR_SECTION_START = .; + *(QB_ATTR_SECTION); + QB_ATTR_SECTION_STOP = .; + } +#endif +} diff --git a/lib/qblog_script_noop.ld b/lib/qblog_script_noop.ld new file mode 100644 index 000000000..f037fcace --- /dev/null +++ b/lib/qblog_script_noop.ld @@ -0,0 +1 @@ +/* this is an empty linker script having a role of a NO-OP link object */ diff --git a/lib/util.c b/lib/util.c index 6181a2594..a510bd1c9 100644 --- a/lib/util.c +++ b/lib/util.c @@ -23,6 +23,7 @@ #include "util_int.h" #include #include +#include #include #include @@ -372,3 +373,12 @@ qb_util_stopwatch_time_split_get(qb_util_stopwatch_t *sw, } return (time_start - time_end) / QB_TIME_NS_IN_USEC; } + +const struct qb_version qb_ver = { + .major = QB_VER_MAJOR, + .minor = QB_VER_MINOR, + .micro = QB_VER_MICRO, + .rest = QB_VER_REST, +}; + +const char *const qb_ver_str = QB_VER_STR; diff --git a/libqb.spec.in b/libqb.spec.in index 3c73d40ca..5dda96705 100644 --- a/libqb.spec.in +++ b/libqb.spec.in @@ -31,7 +31,8 @@ make %{?_smp_mflags} %if 0%{?with_check} %check -VERBOSE=1 make check +make V=1 check \ + && make -C tests/functional/log_internal V=1 check %endif %install diff --git a/tests/Makefile.am b/tests/Makefile.am index fe5474116..df1af81a7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -20,6 +20,9 @@ MAINTAINERCLEANFILES = Makefile.in EXTRA_DIST = CLEANFILES = + +SUBDIRS = functional + AM_CPPFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include noinst_PROGRAMS = bmc bmcpt bms rbreader rbwriter \ diff --git a/tests/functional/GNUmakefile b/tests/functional/GNUmakefile new file mode 100644 index 000000000..1b9de8100 --- /dev/null +++ b/tests/functional/GNUmakefile @@ -0,0 +1,20 @@ +all check: + # do not trigger automatically, it's for on-demand use + @echo "Use 'make $@' within particular subdirectories" +install: force + # definitely not desired to install anything from this subtree +distclean: + # following is a nasty hack to keep "make distclean" succeeding + # (problem mostly arises from shared object files and hence shared + # compiler-generated makefile includes which are swiped when + # processing one subdir and missing as hard error for the other) + @$(MAKE) -C log_external $@ + @mkdir .deps + @touch .deps/log_client.Po .deps/log_interlib.Plo .deps/log_interlib_client.Po + @$(MAKE) -C log_internal $@ + @$(MAKE) -f Makefile $@ SUBDIRS= +%: force + @$(MAKE) -f Makefile $@ +force: ; + +.PHONY: check distclean force install diff --git a/tests/functional/Makefile.am b/tests/functional/Makefile.am new file mode 100644 index 000000000..4d9071f84 --- /dev/null +++ b/tests/functional/Makefile.am @@ -0,0 +1,25 @@ +# Copyright 2017 Red Hat, Inc. +# +# Authors: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +MAINTAINERCLEANFILES = Makefile.in +EXTRA_DIST = GNUmakefile log_test_client.err log_test_interlib_client.err \ + log_test_client.sh log_test_interlib_client.sh \ + log_test_mock.sh syslog-stdout.py \ + log_callsite_bench_gen.py +SUBDIRS = log_external log_internal diff --git a/tests/functional/log.am b/tests/functional/log.am new file mode 100644 index 000000000..f2c6fbcc9 --- /dev/null +++ b/tests/functional/log.am @@ -0,0 +1,58 @@ +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +MAINTAINERCLEANFILES = Makefile.in +CLEANFILES = log_test_client.err.real log_test_interlib_client.err.real \ + ../log_callsite_bench.c + +AM_CPPFLAGS = -D_GNU_SOURCE -I$(top_builddir)/include -I$(top_srcdir)/include + +noinst_PROGRAMS = log_client log_interlib_client +# cannot use {check,noinst}_LTLIBRARIES because it leads to solely static lib +# (this won't get installed anyway, thanks to GNUmakefile rule) +lib_LTLIBRARIES = liblog_inter.la + +log_client_SOURCES = ../log_client.c +# log_client_LDFLAGS/log_client_LDADD to be delivered by the base Makefile.am + +liblog_inter_la_SOURCES = ../log_interlib.c +liblog_inter_la_LDFLAGS = -shared +# liblog_inter_la_LIBADD to be delivered by the base Makefile.am + +log_interlib_client_SOURCES = ../log_interlib_client.c +# this transitively shares link dependencies with liblog_inter.la itself +log_interlib_client_LDADD = $(builddir)/liblog_inter.la + +# actual 'make check' auxiliary definitions +TESTS = ../log_test_client.sh ../log_test_interlib_client.sh +TEST_EXTENSIONS = .sh +log_test.log: $(check_PROGRAMS) + +../log_test_client.log: log_client +../log_test_interlib_client.log: log_interlib_client + +../log_callsite_bench.c: ../log_callsite_bench_gen.py +if HAVE_PYTHON + $(AM_V_GEN)$(PYTHON) $< >$@ +else + @echo 'configure script did not detect python prerequisite' + false +endif + +# vim: ft=automake diff --git a/tests/functional/log_callsite_bench_gen.py b/tests/functional/log_callsite_bench_gen.py new file mode 100755 index 000000000..4cb7e2968 --- /dev/null +++ b/tests/functional/log_callsite_bench_gen.py @@ -0,0 +1,173 @@ +#!/usr/bin/python +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +# expected to work with both Python 2.6+/3+ +from __future__ import print_function + +"""Generate callsite-heavy logging client so as to evaluate use of resources""" + +from getopt import GetoptError, getopt +from math import ceil, floor, log10 +#from pprint import pprint +#from random import shuffle +from sys import argv, exit + +def die(*args, **kwargs): + print(*args, **kwargs) + exit(1) + +def list_to_c_source(worklist, fnc_prefix, width=0): + ret = [] + + while worklist: + item = worklist.pop() + if type(item) is list: + head, children = item + if type(children) is list: + for i, ci in enumerate(children): + ret += list_to_c_source([ci], fnc_prefix, width) + if type(ci) is list: + children[i] = ci[0] + if type(children[i]) is list: + children[i] = children[i][0] + else: + head = item + children = [] + if type(head) is not list: + head = [head] + ret += ["static void {0}_{1:0{2}}(int doit) {{" + .format(fnc_prefix, head[0], width), + "\tif (!doit) return;"] + ret += ["\tqb_log(LOG_ERR, \"{0:0{1}}\");".format(i, width) + for i in head] + ret += ["\t{0}_{1:0{2}}(doit);".format(fnc_prefix, i, width) + for i in reversed(children)] + ret += ["}"] + return ret + +def main(opts, args): + FNC_PREFIX = "fnc" + + try: + CALLSITE_COUNT = int(opts["CALLSITE_COUNT"]) + if not 0 < CALLSITE_COUNT < 10 ** 6: raise ValueError + except ValueError: + die("callsites count can only be a number x, 0 < x < 1e6") + try: + BRANCHING_FACTOR = int(opts["BRANCHING_FACTOR"]) + if not 0 < BRANCHING_FACTOR < 10 ** 3: raise ValueError + except ValueError: + die("branching factor can only be a number x, 0 < x < 1000") + try: + CALLSITES_PER_FNC = int(opts["CALLSITES_PER_FNC"]) + if not 0 < CALLSITES_PER_FNC < 10 ** 3: raise ValueError + except ValueError: + die("callsites-per-fnc count can only be a number x, 0 < x < 1000") + try: + ROUND_COUNT = int(opts["ROUND_COUNT"]) + if not 0 < ROUND_COUNT < 10 ** 6: raise ValueError + except ValueError: + die("round count can only be a number x, 0 < x < 1e6") + + worklist, worklist_len = list(range(0, CALLSITE_COUNT)), CALLSITE_COUNT + #shuffle(worklist) + + #pprint(worklist) + first = worklist[0] + while worklist_len > 1: + item = worklist.pop(); worklist_len -= 1 + reminder = worklist_len % CALLSITES_PER_FNC + parent = (worklist_len - reminder if reminder + else (worklist_len // CALLSITES_PER_FNC - 1) + // BRANCHING_FACTOR * CALLSITES_PER_FNC) + #print("parent {0} (len={1})".format(parent, worklist_len)) + if type(worklist[parent]) is not list: + worklist[parent] = [worklist[parent], []] + if not(reminder): + worklist[parent][1].append(item) # reverses the order! + #worklist[parent][1][:0] = [item] + else: + if type(worklist[parent][0]) is not list: + worklist[parent][0] = [worklist[parent][0]] + #worklist[parent][0].append(item) # reverses the order + worklist[parent][0][1:1] = [item] # parent itself the 1st element + #pprint(worklist) + + width = int(floor(log10(CALLSITE_COUNT))) + 1 + print('\n'.join([ + "/* compile with -lqb OR with -DQB_KILL_ATTRIBUTE_SECTION -l:libqb.so.0 */", + "#include ", + ] + list_to_c_source(worklist, FNC_PREFIX, width) + [ + "int main(int argc, char *argv[]) {", + "\tqb_log_init(\"log_gen_test\", LOG_DAEMON, LOG_INFO);", + "\tqb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE);", + "\tqb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, \"*\", LOG_ERR);", + "\tqb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE);", + "\tfor (int i = 0; i < {0}; i++) {{".format(ROUND_COUNT), + "\t\t{0}_{1:0{2}}(argc);".format(FNC_PREFIX, first, width), + "\t}", + "\tqb_log_fini();", + "\treturn !argc;", + "}" + ] + )) + + +if __name__ == '__main__': + # Full trees for CALLSITES_PER_FNC == 1 (can be trivially extrapolated): + # BF = 2 (binary trees) + # --> C = 7 (3 steps), 15 (4 steps), ..., 127 (6 steps), ... + # (see https://en.wikipedia.org/wiki/Binary_tree#Properties_of_binary_trees) + # BF = 3 (ternary trees) + # --> C = 13 (3 steps), 40 (4 steps), ..., 1093 (6 steps), ... + # (see https://en.wikipedia.org/wiki/Ternary_tree#Properties_of_ternary_trees) + # ... + BRANCHING_FACTOR = 3 + CALLSITES_PER_FNC = 10 + CALLSITE_COUNT = 3640 + ROUND_COUNT = 1000 + try: + opts, args = getopt(argv[1:], + "hc:b:f:r:", + ("help", "callsite-count=", "branching-factor=", + "callsites-per-fnc=", "round-count=")) + for o, a in opts: + if o in ("-h", "--help"): + raise GetoptError("__justhelp__") + elif o in ("-c", "--callsite-count"): CALLSITE_COUNT = a + elif o in ("-b", "--branching-factor"): BRANCHING_FACTOR = a + elif o in ("-f", "--callsites-per-fnc"): CALLSITES_PER_FNC = a + elif o in ("-r", "--round-count"): ROUND_COUNT = a + except GetoptError as err: + if err.msg != "__justhelp__": + print(str(err)) + print("Usage:\n{0} -h|--help\n" + "{0} [-c X|--callsite-count={CALLSITE_COUNT}]" + " [-b Y|--branching-factor={BRANCHING_FACTOR}]\n" + "{1:{2}} [-f Z|--callsites-per-fnc={CALLSITES_PER_FNC}]" + " [-r R|--round-count={ROUND_COUNT}]" + .format(argv[0], '', len(argv[0]), **locals())) + exit(0 if err.msg == "__justhelp__" else 2) + + opts = dict(CALLSITE_COUNT=CALLSITE_COUNT, + BRANCHING_FACTOR=BRANCHING_FACTOR, + CALLSITES_PER_FNC=CALLSITES_PER_FNC, + ROUND_COUNT=ROUND_COUNT) + main(opts, args) diff --git a/tests/functional/log_client.c b/tests/functional/log_client.c new file mode 100644 index 000000000..d5d80890f --- /dev/null +++ b/tests/functional/log_client.c @@ -0,0 +1,93 @@ +/* + * Copyright 2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Pokorny + * + * This file is part of libqb. + * + * libqb is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * libqb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libqb. If not, see . + */ +#include "os_base.h" +#include + +#ifndef NSELFCHECK +QB_LOG_INIT_DATA(linker_contra_log); +#endif + +static const char * +my_tags_stringify(uint32_t tags) +{ + if (qb_bit_is_set(tags, QB_LOG_TAG_LIBQB_MSG_BIT)) { + return "libqb"; + } else { + return "MAIN"; + } +} + +int32_t +main(int32_t argc, char *argv[]) +{ + int tmpfile_fd; + struct stat tmpfile_stat; + char *tmpfile_buf = strdup("linker-log-XXXXXX"); + + qb_log_init("linker-contra-log", LOG_USER, LOG_INFO); + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); + qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, + QB_LOG_FILTER_FILE, "*", LOG_DEBUG); + qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); + + qb_log_tags_stringify_fn_set(my_tags_stringify); + qb_log_format_set(QB_LOG_STDERR, "[%5g|%p] %f:%l:%b"); + +#if 0 + printf("\n==%s consists of: %d, %d, %d, %s==\n\n", qb_ver_str, + qb_ver.major, qb_ver.minor, qb_ver.micro, qb_ver.rest); +#endif + +#if 0 + printf("--\n"); + qb_log_callsites_dump(); + printf("--\n"); +#endif + +#ifndef NLOG + /* Casual test of "user-space" logging. */ + qb_log(LOG_DEBUG, "hello"); +#endif + + /* And now of "library-space" logging, i.e., let libqb generated + an error message on its own behalf, first to see if it will be + logged at all, second if it will be distinguished properly. + The trigger here is as simple as trying to print non-existing + blackbox file. */ + tmpfile_fd = mkstemp(tmpfile_buf); + if (tmpfile_fd == -1) { + qb_perror(LOG_ERR, "creating temporary file"); + exit(EXIT_FAILURE); + } + unlink(tmpfile_buf); + close(tmpfile_fd); +#if 0 + if (stat(tmpfile_buf, &tmpfile_stat) == -1) { + qb_perror(LOG_ERR, "stat'ing nonexistent temporary file"); + exit(EXIT_FAILURE); + } +#endif + qb_log_blackbox_print_from_file(tmpfile_buf); + free(tmpfile_buf); + qb_log_fini(); +} diff --git a/tests/functional/log_external/Makefile.am b/tests/functional/log_external/Makefile.am new file mode 100644 index 000000000..ac0a2ea8e --- /dev/null +++ b/tests/functional/log_external/Makefile.am @@ -0,0 +1,40 @@ +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +include ../log.am + +# while linking with system-wide version of libqb, we are still pursuing +# local in-tree header file, hence we need to link with dynamic linking +# library (which is a prerequisite for using QB_LOG_INIT_DATA defined +# in qblog.h) explicitly +log_client_LDFLAGS = -lqb @client_dlopen_LIBS@ +liblog_inter_la_LIBADD = -lqb @client_dlopen_LIBS@ + +# extras for log_external: comparable, generated callsite-heavy logging clients + +if HAVE_PYTHON +noinst_PROGRAMS += log_callsite_bench_sectionfull log_callsite_bench_sectionless +endif + +log_callsite_bench_sectionfull_SOURCES = ../log_callsite_bench.c +log_callsite_bench_sectionfull_LDFLAGS = -lqb + +log_callsite_bench_sectionless_SOURCES = ../log_callsite_bench.c +log_callsite_bench_sectionless_LDFLAGS = -l:libqb.so.0 +log_callsite_bench_sectionless_CPPFLAGS = -DQB_KILL_ATTRIBUTE_SECTION diff --git a/tests/functional/log_interlib.c b/tests/functional/log_interlib.c new file mode 100644 index 000000000..228a339d8 --- /dev/null +++ b/tests/functional/log_interlib.c @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Pokorny + * + * This file is part of libqb. + * + * libqb is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * libqb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libqb. If not, see . + */ +#include "os_base.h" +#include + +#ifndef NSELFCHECK +QB_LOG_INIT_DATA(linker_contra_log_lib); +#endif + +void foo(void); + +void +foo(void) +{ + int tmpfile_fd; + struct stat tmpfile_stat; + char *tmpfile_buf = strdup("linker-log-XXXXXX"); + +#if 0 + printf("--\n"); + qb_log_callsites_dump(); + printf("--\n"); +#endif + +#ifndef NLIBLOG + /* Casual test of "user-space" logging. */ + qb_log(LOG_INFO, "aloha"); +#endif + + /* And now of "library-space" logging, i.e., let libqb generated + an error message on its own behalf, first to see if it will be + logged at all, second if it will be distinguished properly. + The trigger here is as simple as trying to print non-existing + blackbox file. */ + tmpfile_fd = mkstemp(tmpfile_buf); + if (tmpfile_fd == -1) { + qb_perror(LOG_ERR, "creating temporary file"); + exit(EXIT_FAILURE); + } + unlink(tmpfile_buf); + close(tmpfile_fd); +#if 0 + if (stat(tmpfile_buf, &tmpfile_stat) == -1) { + qb_perror(LOG_ERR, "stat'ing nonexistent temporary file"); + exit(EXIT_FAILURE); + } +#endif + qb_log_blackbox_print_from_file(tmpfile_buf); + free(tmpfile_buf); +} diff --git a/tests/functional/log_interlib_client.c b/tests/functional/log_interlib_client.c new file mode 100644 index 000000000..820df67ce --- /dev/null +++ b/tests/functional/log_interlib_client.c @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Red Hat, Inc. + * + * All rights reserved. + * + * Author: Jan Pokorny + * + * This file is part of libqb. + * + * libqb is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * libqb is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libqb. If not, see . + */ +#include "os_base.h" +#include + +#ifndef NSELFCHECK +QB_LOG_INIT_DATA(linker_contra_log_lib_user); +#endif + +void foo(void); + +static const char * +my_tags_stringify(uint32_t tags) +{ + if (qb_bit_is_set(tags, QB_LOG_TAG_LIBQB_MSG_BIT)) { + return "libqb"; + } else { + return "MAIN"; + } +} + +int +main(int argc, char *argv[]) +{ + qb_log_init("linker-contra-log", LOG_USER, LOG_INFO); + qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); + qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, + QB_LOG_FILTER_FILE, "*", LOG_DEBUG); + qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); + + qb_log_tags_stringify_fn_set(my_tags_stringify); + qb_log_format_set(QB_LOG_STDERR, "[%5g|%p] %f:%l:%b"); + +#if 0 + printf("--\n"); + qb_log_callsites_dump(); + printf("--\n"); +#endif + +#ifndef NLOG + qb_log(LOG_INFO, "BEFORE"); +#endif + foo(); +#ifndef NLOG + qb_log(LOG_INFO, "AFTER"); +#endif + qb_log_fini(); +} diff --git a/tests/functional/log_internal/Makefile.am b/tests/functional/log_internal/Makefile.am new file mode 100644 index 000000000..697cee2a6 --- /dev/null +++ b/tests/functional/log_internal/Makefile.am @@ -0,0 +1,23 @@ +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +include ../log.am + +log_client_LDADD = $(top_builddir)/lib/libqb.la +liblog_inter_la_LIBADD = $(top_builddir)/lib/libqb.la diff --git a/tests/functional/log_test_client.err b/tests/functional/log_test_client.err new file mode 100644 index 000000000..98df44ca7 --- /dev/null +++ b/tests/functional/log_test_client.err @@ -0,0 +1,2 @@ +[MAIN |debug] ../log_client.c:69:hello +[libqb|error] log_blackbox.c:196:qb_log_blackbox_print_from_file: diff --git a/tests/functional/log_test_client.sh b/tests/functional/log_test_client.sh new file mode 100755 index 000000000..9098751f0 --- /dev/null +++ b/tests/functional/log_test_client.sh @@ -0,0 +1,33 @@ +#!/bin/sh +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +# error msg can differ per locale, errno code per system (Hurd begs to differ) +./log_client 2>&1 >/dev/null \ + | sed 's/\(qb_log_blackbox_print_from_file:\).*/\1/' \ + >log_test_client.err.real + +_pipeline='cat ../log_test_client.err' +case "${CPPFLAGS}" in + *-DNLOG*) + _pipeline="${_pipeline} | \ + grep -Ev '^\[MAIN |info] \.\./log_client\.c'";; +esac + +eval "${_pipeline}" | diff -u - log_test_client.err.real diff --git a/tests/functional/log_test_interlib_client.err b/tests/functional/log_test_interlib_client.err new file mode 100644 index 000000000..5b42b29e3 --- /dev/null +++ b/tests/functional/log_test_interlib_client.err @@ -0,0 +1,4 @@ +[MAIN |info] ../log_interlib_client.c:61:BEFORE +[MAIN |info] ../log_interlib.c:47:aloha +[libqb|error] log_blackbox.c:196:qb_log_blackbox_print_from_file: +[MAIN |info] ../log_interlib_client.c:65:AFTER diff --git a/tests/functional/log_test_interlib_client.sh b/tests/functional/log_test_interlib_client.sh new file mode 100755 index 000000000..d06ec989a --- /dev/null +++ b/tests/functional/log_test_interlib_client.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +# error msg can differ per locale, errno code per system (Hurd begs to differ) +./log_interlib_client 2>&1 >/dev/null \ + | sed 's/\(qb_log_blackbox_print_from_file:\).*/\1/' \ + >log_test_interlib_client.err.real + +_pipeline='cat ../log_test_interlib_client.err' +case "${CPPFLAGS}" in + *-DNLOG*) + _pipeline="${_pipeline} | \ + grep -Ev '^\[MAIN \|info] \.\./log_interlib_client\.c'";; + *-DNLIBLOG*) + _pipeline="${_pipeline} | \ + grep -Ev '^\[MAIN \|info\] \.\./log_interlib\.c'";; +esac + +eval "${_pipeline}" | diff -u - log_test_interlib_client.err.real diff --git a/tests/functional/log_test_mock.sh b/tests/functional/log_test_mock.sh new file mode 100755 index 000000000..cdfce1fe2 --- /dev/null +++ b/tests/functional/log_test_mock.sh @@ -0,0 +1,319 @@ +#!/bin/sh +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +# Given the source RPM for libqb, this will run through the basic test matrix +# so as to figure out the outcomes for particular linker (pre-2.29 and 2.29+ +# differing in visibility of orphan section delimiting boundary symbols and +# hence possibly causing harm to the logging facility of libqb) being used +# for particular part of the composite logging system (libqb itself, +# it's direct client / (client library + it's own client that uses logging) +# as well). While this is tailored to Fedora, it should be possible to +# run this testsuite wherever following is present: +# +# - rpm (for parsing archive name embedded in libqb.src.rpm [note that +# rpm2cpio is part of rpm package as well] because the extracted dir +# follows the same naming, which we need to know) +# - mock (https://github.com/rpm-software-management/mock/) +# + dependencies + fedora-27-${arch} configuration for mock +# (or whatever other configuration if some variables below are +# changed appropriately) +# - koji (https://pagure.io/koji/) + dependencies (but binutils packages +# can be precached when downloaded from https://koji.fedoraproject.org/ +# manually) +# - internet connection (but see the above statement for koji, and +# possibly the full package set within the build'n'test underlying +# container can be precached without further details on "how") +# - commons (coreutils, findutils, sed, ...) +# +# The testsuite will not mangle with your host system as mock spawns +# it's somewhat private container for be worked with under the hood. +# +# Note that in order not to get mad when entering the root password anytime +# mock is invoked, you can add the user initiating the test run to the +# 'mock' group. Be aware of the associated security risks, though: +# https://github.com/rpm-software-management/mock/wiki#setup + +set -eu + +# change following as suitable + +arch=x86_64 +mock_args="-r fedora-27-${arch}" +pkg_binutils_228=binutils-2.28-14.fc27 +#pkg_binutils_228=binutils-2.27-23.fc27 # alternatively test with 2.27 ... +pkg_binutils_229=binutils-2.29-6.fc27 +#pkg_binutils_229=binutils-2.29.1-2.fc28 # alternatively test with 2.29.1 + +# +# prettified reporters +# + +do_progress () { printf "\x1b[7m%s\x1b[0m\n" "$*"; } +do_info () { printf "\x1b[36mINFO: %s\x1b[0m\n" "$*"; } +do_warn () { printf "\x1b[31mWARNING: %s\x1b[0m\n" "$*"; } +do_die () { printf "\x1b[31mFATAL: %s\x1b[0m\n" "$*"; exit 1; } + +# +# actual building blocks +# + +# $1, ... $N: packages (and possibly related subpackages) to be downloaded +do_download () { + while test $# -gt 0; do + if test -d "_pkgs/$1" 2>/dev/null; then + do_info "$1 already downloaded" + shift; continue + fi + mkdir -p "_pkgs/$1" + ( cd "_pkgs/$1" && koji download-build --arch="${arch}" "$1" ) + shift + done +} + +# $1, ... $N: descriptors of packages to be installed +do_install () { + while test $# -gt 0; do + if test -d "_pkgs/$1" 2>/dev/null; then + do_install_inner "_pkgs/$1"/*.rpm + else + do_warn "$1 is not downloaded, hence skipped" + fi + shift + done +} + +# $1, ... $N: concrete packages to be installed +do_install_inner () { + _remove_cmd="mock ${mock_args} -- shell \"rpm --nodeps --erase" + _install_cmd="mock ${mock_args}" + while test $# -gt 0; do + case "$1" in + *.src.rpm|*-debuginfo*|*-debugsource*) ;; + *) + _pkg_name="$(basename "$1" | sed 's|\(-[0-9].*\)||')" + _remove_cmd="${_remove_cmd} \'${_pkg_name}\'" + _install_cmd="${_install_cmd} --install \"$1\"";; + esac + shift + done + eval "${_remove_cmd}\"" || : # extra quotation mark intentional + eval "${_install_cmd}" +} + +# $1: full path of srpm to be rebuilt +# $2: %{dist} macro for rpmbuild (distinguishing the builds) +do_buildsrpm () { + _pkg_descriptor="$(basename "$1" | sed 's|\.src\.rpm$||')" + # need to prune due to possible duplicates caused by differing %{dist} + rm -f -- "_pkgs/${_pkg_descriptor}"/* + mock ${mock_args} -Nn --define "dist $2" --define '_without_check 1' \ + --resultdir "_pkgs/${_pkg_descriptor}" --rebuild "$1" +} + +# $1: full path srpm to be rebuilt +# $2: extra (presumably) variable assignments for the make goal invocation +do_compile_interlib () { + mock ${mock_args} --shell \ + "find \"builddir/build/BUILD/$1/tests/functional\" \ + \( -name log_internal -o -name '*.c' \) -prune \ + -o -name '*liblog_inter*' \ + -exec rm -- {} \;" + mock ${mock_args} --shell "( cd \"builddir/build/BUILD/$1\"; ./configure )" + mock ${mock_args} --shell \ + "make -C \"builddir/build/BUILD/$1/tests/functional/log_external\" \ + liblog_inter.la $2" +} + +# $1: full path srpm to be rebuilt +# $2: which type of client to work with (client/interclient) +# $3: base (on-host) directory for test results +# $4: output file to capture particular test result +# $5: extra (presumably) variable assignments for the make goal invocation +do_compile_and_test_client () { + _result=$4 + case "$2" in + interclient) + _logfile=log_test_interlib_client + mock ${mock_args} --shell \ + "find \"builddir/build/BUILD/$1/tests/functional\" \ + \( -name log_internal -o -name '*.err' -o -name '*.c' \) -prune \ + -o \( -name '*log_interlib_client*' -o -name \"${_logfile}.log\" \) \ + -exec rm -- {} \;" + ;; + client|*) + _logfile=log_test_client + mock ${mock_args} --shell \ + "find \"builddir/build/BUILD/$1/tests/functional\" \ + \( -name log_internal -o -name '*.err' -o -name '*.c' \) -prune \ + -o \( -name '*log_client*' -o -name \"${_logfile}.log\" \) \ + -exec rm -- {} \;" + ;; + esac + mock ${mock_args} --copyin "syslog-stdout.py" "builddir" + mock ${mock_args} --shell "( cd \"builddir/build/BUILD/$1\"; ./configure )" + mock ${mock_args} --shell \ + "python3 builddir/syslog-stdout.py \ + >\"builddir/build/BUILD/$1/tests/functional/log_external/.syslog\" & \ + { sleep 2; make -C \"builddir/build/BUILD/$1/tests/functional/log_external\" \ + check-TESTS \"TESTS=../${_logfile}.sh\" $5; } \ + && ! test -s \"builddir/build/BUILD/$1/tests/functional/log_external/.syslog\"; \ + ret_ec=\$?; \ + ( cd \"builddir/build/BUILD/$1/tests/functional/log_external\"; \ + cat .syslog >> test-suite.log; \ + echo SYSLOG-begin; cat .syslog; echo SYSLOG-end ); \ + ret () { return \$1; }; ret \${ret_ec}" \ + && _result="${_result}_good" \ + || _result="${_result}_bad" + mock ${mock_args} --copyout \ + "builddir/build/BUILD/$1/tests/functional/log_external/test-suite.log" \ + "$3/${_result}" +} + +do_shell () { + mock ${mock_args} --shell +} + +# $1, ... $N: "argv" +do_proceed () { + + _makevars= + _resultsdir_tag= + _selfcheck=1 + _clientlogging=1 + _interliblogging=1 + while :; do + case "$1" in + shell) shift; do_shell "$@"; return;; + -nsc) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _selfcheck=0;; + -ncl) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _clientlogging=0;; + -nil) _resultsdir_tag="${_resultsdir_tag}$1"; shift; _interliblogging=0;; + -*) do_die "Uknown option: $1";; + *) break;; + esac + done + + if test -n "${_resultsdir_tag}"; then + _makevars="CPPFLAGS=\"$(test "${_selfcheck}" -eq 1 || printf %s ' -DNSELFCHECK') \ + $(test "${_clientlogging}" -eq 1 || printf %s ' -DNLOG') \ + $(test "${_interliblogging}" -eq 1 || printf %s ' -DNLIBLOG')\"" + _makevars=$(echo ${_makevars}) + fi + + test -s "$1" || do_die "Not an input file: $1" + _libqb_descriptor_path="$1" + _libqb_descriptor="$(basename "${_libqb_descriptor_path}" \ + | sed 's|\.src\.rpm$||')" + _libqb_descriptor_archive="$(rpm -q --qf '[%{FILENAMES}\n]' \ + -p "${_libqb_descriptor_path}" \ + | sed -n '/\.tar/{s|\.tar\.[^.]*$||;p;q}')" + + _resultsdir="_results/$(date '+%y%m%d_%H%M%S')_${_libqb_descriptor}${_resultsdir_tag}" + mkdir -p "${_resultsdir}" + rm -f -- "${_resultsdir}/*" + + _dist= + _outfile= + _outfile_client= + _outfile_qb= + + do_download "${pkg_binutils_228}" "${pkg_binutils_229}" + + for _pkg_binutils_libqb in "${pkg_binutils_228}" "${pkg_binutils_229}"; do + + case "${_pkg_binutils_libqb}" in + ${pkg_binutils_228}) _outfile_qb="qb+"; _dist=.binutils228;; + ${pkg_binutils_229}) _outfile_qb="qb-"; _dist=.binutils229;; + *) _outfile_qb="?";; + esac + + do_progress "installing ${_pkg_binutils_libqb} so as to build" \ + "libqb [${_outfile_qb}]" + do_install "${_pkg_binutils_libqb}" + + do_progress "building ${_libqb_descriptor_path} with" \ + "${_pkg_binutils_libqb} [${_outfile_qb}]" + do_buildsrpm "${_libqb_descriptor_path}" "${_dist}" + + do_progress "installing ${_libqb_descriptor}-based packages" \ + "built with ${_pkg_binutils_libqb} [${_outfile_qb}]" + do_install "${_libqb_descriptor}" + # from now on, we can work fully offline, also to speed + # the whole thing up (and not to bother the mirrors) + mock_args="${mock_args} --offline" + + for _pkg_binutils_interlib in none "${pkg_binutils_228}" "${pkg_binutils_229}"; do + + case "${_pkg_binutils_interlib}" in + none) _outfile="${_outfile_qb}";; + ${pkg_binutils_228}) _outfile="${_outfile_qb}_il+";; + ${pkg_binutils_229}) _outfile="${_outfile_qb}_il-";; + *) _outfile="${_outfile_qb}_?";; + esac + + case "${_pkg_binutils_interlib}" in + none) ;; + *) + do_progress "installing ${_pkg_binutils_interlib}" \ + "so as to build interlib [${_outfile}]" + do_install "${_pkg_binutils_interlib}" + + do_progress "building interlib with ${_libqb_descriptor_archive}" \ + "+ ${_pkg_binutils_interlib} [${_outfile}]" \ + "{${_makevars}}" + do_compile_interlib "${_libqb_descriptor_archive}" "${_makevars}" + ;; + esac + + for _pkg_binutils_client in "${pkg_binutils_228}" "${pkg_binutils_229}"; do + + _client=client + test "${_pkg_binutils_interlib}" = none || _client=interclient + + case "${_pkg_binutils_client}" in + ${pkg_binutils_228}) _outfile_client="${_outfile}_c+";; + ${pkg_binutils_229}) _outfile_client="${_outfile}_c-";; + *) _outfile_client="${_outfile}_?";; + esac + + do_progress "installing ${_pkg_binutils_client}" \ + "so as to build ${_client} [${_outfile_client}]" + do_install "${_pkg_binutils_client}" + do_progress "building ${_client} with ${_libqb_descriptor_archive}" \ + "+ ${_pkg_binutils_client} [${_outfile_client}]" \ + "{${_makevars}}" + do_compile_and_test_client "${_libqb_descriptor_archive}" \ + "${_client}" "${_resultsdir}" \ + "${_outfile_client}" "${_makevars}" + done + done + done +} + +{ test $# -eq 0 || test "$1" = -h || test "$1" = --help; } \ + && printf '%s\n %s\n %s\n %s\n %s\n %s\n %s\n %s\n' \ + "usage: $0 {[-n{sc,cl,il}]* | shell}" \ + "- use '-nsc' to suppress optional self-check (\"see whole story\")" \ + "- use '-ncl' to suppress client-side logging" \ + "- use '-nil' to suppress interlib-side logging" \ + "- 'make -C ../.. srpm' (or so) can generate the requested input" \ + " (in that case, apparently, refer to '../../libqb-X.src.rpm')" \ + "- _pkgs dir caches (intermediate or not) packages to work with" \ + "- results stored in '_results/_[_]'" \ + || do_proceed "$@" diff --git a/tests/functional/syslog-stdout.py b/tests/functional/syslog-stdout.py new file mode 100755 index 000000000..64baf4c13 --- /dev/null +++ b/tests/functional/syslog-stdout.py @@ -0,0 +1,51 @@ +#!/usr/bin/python3 +# Copyright 2017 Red Hat, Inc. +# +# Author: Jan Pokorny +# +# This file is part of libqb. +# +# libqb is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation, either version 2.1 of the License, or +# (at your option) any later version. +# +# libqb is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with libqb. If not, see . + +"""Simple /dev/log to stdout forwarding""" + +import socket +from atexit import register +from os import remove +from sys import argv + +# no locking, but anyway +try: + remove("/dev/log") +except FileNotFoundError: + pass +sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) +sock.bind("/dev/log") + +def shutdown(): + sock.close() + remove("/dev/log") + +def main(*argv): + register(shutdown) + while True: + try: + b = sock.recv(4096) + # flushing is crucial here + print(">>> " + str(b, 'ascii').split(' ', 3)[-1], flush=True) + except IOError: + pass + +if __name__ == '__main__': + main(*argv)