diff options
Diffstat (limited to 'nxcomp')
-rw-r--r-- | nxcomp/.gitignore | 4 | ||||
-rw-r--r-- | nxcomp/Makefile.am | 2 | ||||
-rw-r--r-- | nxcomp/configure.ac | 26 | ||||
l--------- | nxcomp/m4/ax_cxx_compile_stdcxx.m4 | 1 | ||||
l--------- | nxcomp/m4/ax_cxx_compile_stdcxx_11.m4 | 1 | ||||
-rw-r--r-- | nxcomp/m4/ax_pthread.m4 | 485 | ||||
-rw-r--r-- | nxcomp/src/Log.cpp | 121 | ||||
-rw-r--r-- | nxcomp/src/Log.h | 545 | ||||
-rw-r--r-- | nxcomp/src/Loop.cpp | 97 | ||||
-rw-r--r-- | nxcomp/src/Makefile.am | 9 | ||||
-rw-r--r-- | nxcomp/test/Makefile.am | 22 | ||||
-rw-r--r-- | nxcomp/test/logging_test.cpp | 224 | ||||
-rw-r--r-- | nxcomp/test/logging_test.h | 121 |
13 files changed, 1656 insertions, 2 deletions
diff --git a/nxcomp/.gitignore b/nxcomp/.gitignore index 019202342..1d00b755b 100644 --- a/nxcomp/.gitignore +++ b/nxcomp/.gitignore @@ -19,3 +19,7 @@ m4/lt~obsolete.m4 nxcomp.pc src/Makefile src/Makefile.in +test/.deps/ +test/Makefile +test/Makefile.in +test/logging_test diff --git a/nxcomp/Makefile.am b/nxcomp/Makefile.am index ae5991c3d..c1f6226bc 100644 --- a/nxcomp/Makefile.am +++ b/nxcomp/Makefile.am @@ -1,4 +1,4 @@ -SUBDIRS = src +SUBDIRS = src test pkgconfig_DATA = nxcomp.pc diff --git a/nxcomp/configure.ac b/nxcomp/configure.ac index 0b30915c2..3ae81f0d1 100644 --- a/nxcomp/configure.ac +++ b/nxcomp/configure.ac @@ -63,9 +63,34 @@ if test "$FreeBSD" = yes; then CPPFLAGS="$CPPFLAGS -I/usr/local/include" fi +AX_PTHREAD([], AC_MSG_ERROR([no POSIX threads support detected])) + # If in_addr_t is not defined use unsigned int. AC_CHECK_TYPES([in_addr_t], [], [], [[#include <netinet/in.h>]]) +AC_ARG_ENABLE([cxx11], + [AS_HELP_STRING([--enable-cxx11], + [enable optional features requiring C++11 support (disabled by default)])], + [AS_IF([test x$enableval = xyes], + [AX_CXX_COMPILE_STDCXX_11([], [mandatory])])]) + +# Check if std::put_time is available. +AC_MSG_CHECKING([if std::put_time is available]) +AC_COMPILE_IFELSE([AC_LANG_PROGRAM( +[[ +#include <iomanip> +#include <ctime> +]], +[[ +std::time_t t = std::time(NULL); +std::tm tm = *std::localtime(&t); +(void) std::put_time(&tm, "%c"); +]])], + [AC_MSG_RESULT([yes]) + AC_DEFINE(HAVE_STD_PUT_TIME, [1], + [Use std::put_time to format times, must be made available by the compiler if turned on.])], + [AC_MSG_RESULT([no])]) + AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug], [enable to get info session log output (disabled by default)])], @@ -83,6 +108,7 @@ AC_ARG_ENABLE([valgrind], AC_CONFIG_FILES([ Makefile src/Makefile +test/Makefile nxcomp.pc ]) diff --git a/nxcomp/m4/ax_cxx_compile_stdcxx.m4 b/nxcomp/m4/ax_cxx_compile_stdcxx.m4 new file mode 120000 index 000000000..28ebfd1a6 --- /dev/null +++ b/nxcomp/m4/ax_cxx_compile_stdcxx.m4 @@ -0,0 +1 @@ +../../m4/ax_cxx_compile_stdcxx.m4
\ No newline at end of file diff --git a/nxcomp/m4/ax_cxx_compile_stdcxx_11.m4 b/nxcomp/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 120000 index 000000000..5fbe8790d --- /dev/null +++ b/nxcomp/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1 @@ +../../m4/ax_cxx_compile_stdcxx_11.m4
\ No newline at end of file diff --git a/nxcomp/m4/ax_pthread.m4 b/nxcomp/m4/ax_pthread.m4 new file mode 100644 index 000000000..5fbf9fe0d --- /dev/null +++ b/nxcomp/m4/ax_pthread.m4 @@ -0,0 +1,485 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also to link with them as well. For example, you might link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threaded programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to +# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <https://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 24 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_REQUIRE([AC_PROG_CC]) +AC_REQUIRE([AC_PROG_SED]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on Tru64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then + ax_pthread_save_CC="$CC" + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"]) + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS]) + AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes]) + AC_MSG_RESULT([$ax_pthread_ok]) + if test "x$ax_pthread_ok" = "xno"; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + CC="$ax_pthread_save_CC" + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64 +# (Note: HP C rejects this with "bad form for `-t' option") +# -pthreads: Solaris/gcc (Note: HP C also rejects) +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads and +# -D_REENTRANT too), HP C (must be checked before -lpthread, which +# is present but should not be used directly; and before -mthreads, +# because the compiler interprets this as "-mt" + "-hreads") +# -mthreads: Mingw32/gcc, Lynx/gcc +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case $host_os in + + freebsd*) + + # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) + # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) + + ax_pthread_flags="-kthread lthread $ax_pthread_flags" + ;; + + hpux*) + + # From the cc(1) man page: "[-mt] Sets various -D flags to enable + # multi-threading and also sets -lpthread." + + ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags" + ;; + + openedition*) + + # IBM z/OS requires a feature-test macro to be defined in order to + # enable POSIX threads at all, so give the user a hint if this is + # not set. (We don't define these ourselves, as they can affect + # other portions of the system API in unpredictable ways.) + + AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING], + [ +# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS) + AX_PTHREAD_ZOS_MISSING +# endif + ], + [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])]) + ;; + + solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (N.B.: The stubs are missing + # pthread_cleanup_push, or rather a function called by this macro, + # so we could check for that, but who knows whether they'll stub + # that too in a future libc.) So we'll check first for the + # standard Solaris way of linking pthreads (-mt -lpthread). + + ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags" + ;; +esac + +# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC) + +AS_IF([test "x$GCC" = "xyes"], + [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"]) + +# The presence of a feature test macro requesting re-entrant function +# definitions is, on some systems, a strong hint that pthreads support is +# correctly enabled + +case $host_os in + darwin* | hpux* | linux* | osf* | solaris*) + ax_pthread_check_macro="_REENTRANT" + ;; + + aix*) + ax_pthread_check_macro="_THREAD_SAFE" + ;; + + *) + ax_pthread_check_macro="--" + ;; +esac +AS_IF([test "x$ax_pthread_check_macro" = "x--"], + [ax_pthread_check_cond=0], + [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"]) + +# Are we compiling with Clang? + +AC_CACHE_CHECK([whether $CC is Clang], + [ax_cv_PTHREAD_CLANG], + [ax_cv_PTHREAD_CLANG=no + # Note that Autoconf sets GCC=yes for Clang as well as GCC + if test "x$GCC" = "xyes"; then + AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG], + [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */ +# if defined(__clang__) && defined(__llvm__) + AX_PTHREAD_CC_IS_CLANG +# endif + ], + [ax_cv_PTHREAD_CLANG=yes]) + fi + ]) +ax_pthread_clang="$ax_cv_PTHREAD_CLANG" + +ax_pthread_clang_warning=no + +# Clang needs special handling, because older versions handle the -pthread +# option in a rather... idiosyncratic way + +if test "x$ax_pthread_clang" = "xyes"; then + + # Clang takes -pthread; it has never supported any other flag + + # (Note 1: This will need to be revisited if a system that Clang + # supports has POSIX threads in a separate library. This tends not + # to be the way of modern systems, but it's conceivable.) + + # (Note 2: On some systems, notably Darwin, -pthread is not needed + # to get POSIX threads support; the API is always present and + # active. We could reasonably leave PTHREAD_CFLAGS empty. But + # -pthread does define _REENTRANT, and while the Darwin headers + # ignore this macro, third-party headers might not.) + + PTHREAD_CFLAGS="-pthread" + PTHREAD_LIBS= + + ax_pthread_ok=yes + + # However, older versions of Clang make a point of warning the user + # that, in an invocation where only linking and no compilation is + # taking place, the -pthread option has no effect ("argument unused + # during compilation"). They expect -pthread to be passed in only + # when source code is being compiled. + # + # Problem is, this is at odds with the way Automake and most other + # C build frameworks function, which is that the same flags used in + # compilation (CFLAGS) are also used in linking. Many systems + # supported by AX_PTHREAD require exactly this for POSIX threads + # support, and in fact it is often not straightforward to specify a + # flag that is used only in the compilation phase and not in + # linking. Such a scenario is extremely rare in practice. + # + # Even though use of the -pthread flag in linking would only print + # a warning, this can be a nuisance for well-run software projects + # that build with -Werror. So if the active version of Clang has + # this misfeature, we search for an option to squash it. + + AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG], + [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown + # Create an alternate version of $ac_link that compiles and + # links in two steps (.c -> .o, .o -> exe) instead of one + # (.c -> exe), because the warning occurs only in the second + # step + ax_pthread_save_ac_link="$ac_link" + ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g' + ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"` + ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)" + ax_pthread_save_CFLAGS="$CFLAGS" + for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do + AS_IF([test "x$ax_pthread_try" = "xunknown"], [break]) + CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS" + ac_link="$ax_pthread_save_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [ac_link="$ax_pthread_2step_ac_link" + AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])], + [break]) + ]) + done + ac_link="$ax_pthread_save_ac_link" + CFLAGS="$ax_pthread_save_CFLAGS" + AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no]) + ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try" + ]) + + case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in + no | unknown) ;; + *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;; + esac + +fi # $ax_pthread_clang = yes + +if test "x$ax_pthread_ok" = "xno"; then +for ax_pthread_try_flag in $ax_pthread_flags; do + + case $ax_pthread_try_flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -mt,pthread) + AC_MSG_CHECKING([whether pthreads work with -mt -lpthread]) + PTHREAD_CFLAGS="-mt" + PTHREAD_LIBS="-lpthread" + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag]) + PTHREAD_CFLAGS="$ax_pthread_try_flag" + ;; + + pthread-config) + AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no]) + AS_IF([test "x$ax_pthread_config" = "xno"], [continue]) + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag]) + PTHREAD_LIBS="-l$ax_pthread_try_flag" + ;; + esac + + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> +# if $ax_pthread_check_cond +# error "$ax_pthread_check_macro must be defined" +# endif + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + AC_MSG_RESULT([$ax_pthread_ok]) + AS_IF([test "x$ax_pthread_ok" = "xyes"], [break]) + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = "xyes"; then + ax_pthread_save_CFLAGS="$CFLAGS" + ax_pthread_save_LIBS="$LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_CACHE_CHECK([for joinable pthread attribute], + [ax_cv_PTHREAD_JOINABLE_ATTR], + [ax_cv_PTHREAD_JOINABLE_ATTR=unknown + for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>], + [int attr = $ax_pthread_attr; return attr /* ; */])], + [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break], + []) + done + ]) + AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \ + test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \ + test "x$ax_pthread_joinable_attr_defined" != "xyes"], + [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE], + [$ax_cv_PTHREAD_JOINABLE_ATTR], + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + ax_pthread_joinable_attr_defined=yes + ]) + + AC_CACHE_CHECK([whether more special flags are required for pthreads], + [ax_cv_PTHREAD_SPECIAL_FLAGS], + [ax_cv_PTHREAD_SPECIAL_FLAGS=no + case $host_os in + solaris*) + ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS" + ;; + esac + ]) + AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \ + test "x$ax_pthread_special_flags_added" != "xyes"], + [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS" + ax_pthread_special_flags_added=yes]) + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + [ax_cv_PTHREAD_PRIO_INHERIT], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]], + [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \ + test "x$ax_pthread_prio_inherit_defined" != "xyes"], + [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.]) + ax_pthread_prio_inherit_defined=yes + ]) + + CFLAGS="$ax_pthread_save_CFLAGS" + LIBS="$ax_pthread_save_LIBS" + + # More AIX lossage: compile with *_r variant + if test "x$GCC" != "xyes"; then + case $host_os in + aix*) + AS_CASE(["x/$CC"], + [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6], + [#handle absolute path differently from PATH based program lookup + AS_CASE(["x$CC"], + [x/*], + [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])], + [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])]) + ;; + esac + fi +fi + +test -n "$PTHREAD_CC" || PTHREAD_CC="$CC" + +AC_SUBST([PTHREAD_LIBS]) +AC_SUBST([PTHREAD_CFLAGS]) +AC_SUBST([PTHREAD_CC]) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test "x$ax_pthread_ok" = "xyes"; then + ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/nxcomp/src/Log.cpp b/nxcomp/src/Log.cpp new file mode 100644 index 000000000..951c10a04 --- /dev/null +++ b/nxcomp/src/Log.cpp @@ -0,0 +1,121 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ +/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> */ +/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de> */ +/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de> */ +/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/ +/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ +/* */ +/* NXCOMP, NX protocol compression and NX extensions to this software */ +/* are copyright of the aforementioned persons and companies. */ +/* */ +/* Redistribution and use of the present software is allowed according */ +/* to terms specified in the file LICENSE.nxcomp which comes in the */ +/* source distribution. */ +/* */ +/* All rights reserved. */ +/* */ +/* NOTE: This software has received contributions from various other */ +/* contributors, only the core maintainers and supporters are listed as */ +/* copyright holders. Please contact us, if you feel you should be listed */ +/* as copyright holder, as well. */ +/* */ +/**************************************************************************/ + + +#include <stdio.h> +#include <iostream> +#include <fstream> +#include <sstream> +#include <iomanip> +#include <unistd.h> + +#include "Log.h" +#include "config.h" + +NXLog nx_log; + + +bool NXLog::will_log() const +{ + std::map<std::string, NXLogLevel>::const_iterator item = per_file_levels_.find(current_file()); + + if ( item != per_file_levels_.end() ) + { + return current_level() <= item->second; + } + else + { + return current_level() <= level(); + } +} + + +std::string NXLog::stamp_to_string(const NXLogStamp& stamp) const +{ + std::ostringstream oss; + + static const char* level_names[] = { + "FATAL", + "ERROR", + "WARN ", + "INFO ", + "DEBUG" + }; + + if ( log_level() ) + oss << ((stamp.level() >=0 && stamp.level() < NXLOG_LEVEL_COUNT ) ? level_names[stamp.level()] : "???") << " "; + + if ( log_time() ) + { + struct timeval timestamp = stamp.timestamp(); + struct tm timeinfo; + + localtime_r(×tamp.tv_sec, &timeinfo); + + if ( log_unix_time() ) + { + oss << timestamp.tv_sec; + } + else + { + #if HAVE_STD_PUT_TIME + oss << " " << std::put_time(&timeinfo, "%Y/%m/%d %H:%M:%S"); + #else + oss << timestamp.tv_sec; + #endif + } + + oss << "." << std::setw(3) << std::setfill('0') << (int)(timestamp.tv_usec / 1000) << " "; + } + + if ( log_location() ) + oss << stamp.file() << "/" << stamp.function() << ":" << stamp.line() << " "; + + if ( log_thread_id() ) + { + if ( thread_name().empty() ) + oss << getpid() << "/" << pthread_self() << " "; + else + oss << "[" << thread_name() << "] "; + } + + return oss.str(); +} + +NXLog& operator<< (NXLog& out, const NXLogStamp& value) +{ + out.current_level( value.level() ); + out.current_file( value.file() ); + + // Writing an NXLogStamp to the stream indicates the start of a new entry. + // If there's any content in the buffer, create a new entry in the output + // queue. + if ( out.synchronized() ) + out.new_stack_entry(); + + out << out.stamp_to_string(value); + + return out; +} diff --git a/nxcomp/src/Log.h b/nxcomp/src/Log.h new file mode 100644 index 000000000..aed929b31 --- /dev/null +++ b/nxcomp/src/Log.h @@ -0,0 +1,545 @@ +/**************************************************************************/ +/* */ +/* Copyright (c) 2001, 2011 NoMachine (http://www.nomachine.com) */ +/* Copyright (c) 2008-2014 Oleksandr Shneyder <o.shneyder@phoca-gmbh.de> */ +/* Copyright (c) 2014-2016 Ulrich Sibiller <uli42@gmx.de> */ +/* Copyright (c) 2014-2016 Mihai Moldovan <ionic@ionic.de> */ +/* Copyright (c) 2011-2016 Mike Gabriel <mike.gabriel@das-netzwerkteam.de>*/ +/* Copyright (c) 2015-2016 Qindel Group (http://www.qindel.com) */ +/* */ +/* NXCOMP, NX protocol compression and NX extensions to this software */ +/* are copyright of the aforementioned persons and companies. */ +/* */ +/* Redistribution and use of the present software is allowed according */ +/* to terms specified in the file LICENSE.nxcomp which comes in the */ +/* source distribution. */ +/* */ +/* All rights reserved. */ +/* */ +/* NOTE: This software has received contributions from various other */ +/* contributors, only the core maintainers and supporters are listed as */ +/* copyright holders. Please contact us, if you feel you should be listed */ +/* as copyright holder, as well. */ +/* */ +/**************************************************************************/ + + +#ifndef NXLog_H +#define NXLog_H + +#include <iostream> +#include <fstream> +#include <sstream> +#include <sys/time.h> + +#include <map> +#include <stdlib.h> +#include <string.h> +#include <signal.h> +#include <assert.h> +#include <stack> + +/** Log severity level */ +enum NXLogLevel +{ + NXFATAL, + NXERROR, + NXWARNING, + NXINFO, + NXDEBUG, + NXLOG_LEVEL_COUNT +}; + + +/** + * Log timestamp class + * + * Stores the timestamp, file, function, line number and log level. + * Acts as a manipulator on the NXLog class, telling it a new log + * severity level. For instance: + * + * nx_log << NXLogStamp(...,NXINFO) + * + * Tells nx_log that now NXINFO type messages are being logged. This + * will be applied until a new NXLogStamp with a different level + * is sent to the NXLog. + */ +class NXLogStamp +{ + private: + std::string file_; + std::string function_; + size_t line_; + NXLogLevel level_; + struct timeval timestamp_; + + public: + /** File where the event occurred */ + std::string file() const + { + return file_; + } + + /** Function where the event occurred */ + std::string function() const + { + return function_; + } + + /** Line where the event occurred */ + size_t line() const + { + return line_; + } + + /** Severity level of the event */ + NXLogLevel level() const + { + return level_; + } + + /** Time of the event */ + struct timeval timestamp() const + { + return timestamp_; + } + + + NXLogStamp(const char *file, const char *function, int line, NXLogLevel level) + { + file_ = std::string(file); + function_ = std::string(function); + line_ = line; + level_ = level; + gettimeofday(×tamp_, NULL); + } + +}; + + +/** + * Log class + * + * Logs events to a stream, filters by file/level + */ +class NXLog +{ +#ifdef INTERNAL_LOGGING_TEST + protected: +#endif + NXLogLevel level_; + + std::ostream *stream_; + std::map< std::string, NXLogLevel > per_file_levels_; + bool synchronized_; + size_t thread_buffer_size_; + pthread_mutex_t output_lock_; + pthread_key_t tls_key_; + + bool log_level_; + bool log_time_; + bool log_unix_time_; + bool log_location_; + bool log_thread_id_; + + typedef struct per_thread_data_s + { + NXLogLevel current_level; + std::string* current_file; + std::string* thread_name; + std::stack<std::stringstream*> buffer; + NXLog* log_obj; + } per_thread_data; + + + static void free_thread_data(void* arg) + { + per_thread_data *pdt = (per_thread_data*)arg; + + if ( !pdt ) + return; + + if ( pdt->log_obj ) { + // Ensure the buffer is flushed before thread deletion + pdt->log_obj->flush(pdt); + } + + delete pdt->current_file; + delete pdt->thread_name; + + while (!pdt->buffer.empty()) { + (void) pdt->buffer.pop (); + } + + delete pdt; + } + + per_thread_data* get_data_int() const + { + per_thread_data *ret = NULL; + + if ( (ret = (per_thread_data*)pthread_getspecific(tls_key_)) == NULL ) + { + ret = new per_thread_data; + ret->current_level = NXDEBUG; + ret->current_file = new std::string(); + ret->thread_name = new std::string(); + ret->log_obj = const_cast<NXLog*>(this); + pthread_setspecific(tls_key_, ret); + } + + return ret; + } + + per_thread_data* get_data() + { + return get_data_int(); + } + + const per_thread_data* get_data() const + { + return get_data_int(); + } + + /** Convert NXLogStamp to string according to the current configuration */ + std::string stamp_to_string(const NXLogStamp& stamp) const; + + void new_stack_entry() + { + per_thread_data *pdt = get_data(); + pdt->buffer.push(new std::stringstream()); + } + + /** + * Internal flush function + * + * When a thread is being terminated and free_thread_data gets called, + * the TLS key gets set to NULL before the call to free_thread_data, + * and the destructor function gets the old value. + * + * This means that get_data() stops working correctly, and we need + * to be able to pass the old pointer. + */ + virtual /* Note: this function needs to be virtual for the logging test application. Don't remove. */ + void flush(per_thread_data *pdt) + { + /* + * Block all signals until we are dong printing data. + * Ensures that a signal handler won't interrupt us + * and overwrite the buffer data mid-print, leading + * to confusing output. + */ + sigset_t orig_signal_mask, + tmp_signal_mask; + sigemptyset(&orig_signal_mask); + + /* Set up new mask to block all signals. */ + sigfillset(&tmp_signal_mask); + + /* Block all signals. */ + pthread_sigmask(SIG_BLOCK, &tmp_signal_mask, &orig_signal_mask); + + if (!pdt->buffer.empty ()) { + const std::string str = pdt->buffer.top()->str(); + + if (!str.empty()) + { + pthread_mutex_lock(&output_lock_); + (*stream()) << str; + pthread_mutex_unlock(&output_lock_); + } + + /* Remove from stack. */ + pdt->buffer.pop(); + } + + /* Restore old signal mask. */ + pthread_sigmask(SIG_SETMASK, &orig_signal_mask, NULL); + } + + + public: + NXLog() + { + stream_ = &std::cerr; + level_ = NXWARNING; + synchronized_ = true; + thread_buffer_size_ = 1024; + log_level_ = false; + log_time_ = false; + log_unix_time_ = false; + log_location_ = false; + log_thread_id_ = false; + + if ( pthread_key_create(&tls_key_, free_thread_data) != 0 ) + { + std::cerr << "pthread_key_create failed" << std::endl; + abort(); + } + + } + + ~NXLog() + { + per_thread_data *pdt = get_data(); + + // Flush any remaining output and delete TLS data + free_thread_data(pdt); + + pthread_key_delete(tls_key_); + + if ((stream_) && (stream_ != &std::cerr)) { + delete stream_; + } + } + + /** Minimum severity level to output */ + NXLogLevel level() const + { + return level_; + } + + void level(NXLogLevel level) + { + level_ = level; + } + + + /** Current severity level */ + NXLogLevel current_level() const + { + return get_data()->current_level; + } + + void current_level(NXLogLevel level) + { + get_data()->current_level = level; + } + + /** Source file from which messages are currently originating */ + std::string current_file() const + { + return *get_data()->current_file; + } + + void current_file(std::string val) + { + *get_data()->current_file = val; + } + + std::ostream* stream() const + { + return stream_; + } + + void stream(std::ostream *stream) + { + flush(); + stream_ = stream; + } + + bool synchronized() const { + return synchronized_; + } + + void synchronized(bool val) { + synchronized_ = val; + } + + bool log_level() const + { + return log_level_; + } + + void log_level(bool val) + { + log_level_ = val; + } + + bool log_time() const + { + return log_time_; + } + + void log_time(bool val) + { + log_time_ = val; + } + + bool log_unix_time() const + { + return log_unix_time_; + } + + void log_unix_time(bool val) + { + log_unix_time_ = val; + } + + bool log_location() const + { + return log_location_; + } + + void log_location(bool val) + { + log_location_ = val; + } + + bool log_thread_id() const + { + return log_thread_id_; + } + + void log_thread_id(bool val) + { + log_thread_id_ = val; + } + + + void flush() + { + per_thread_data *pdt = get_data(); + flush(pdt); + } + + std::string thread_name() const + { + return *get_data()->thread_name; + } + + void thread_name(std::string str) + { + *get_data()->thread_name = str; + } + + void thread_name(const char *str) + { + *get_data()->thread_name = str; + } + + /** + * True if a message sent to the NXLog object will be sent to the output + * + * This considers two things: + * + * If there's a per-file log level, then it is used + * Otherwise the global log level is used. + * + * If the log level permits the current message to be output, then the + * return value is true. + */ + bool will_log() const; + + + /** + * This catches std::flush + */ + NXLog& operator<<(std::ostream& (*F)(std::ostream&)) + { + if ( will_log() ) + { + if ( synchronized() ) + { + per_thread_data *pdt = get_data(); + assert (!pdt->buffer.empty ()); + (*pdt->buffer.top()) << F; + flush(); + } + else + { + *(stream()) << F; + } + } + + return *this; + } + + template<typename T> + friend NXLog& operator<<(NXLog& out, const T& value); + + friend NXLog& operator<< (NXLog& out, const NXLogStamp& value); +}; + + +extern NXLog nx_log; + + +#define nxstamp(l) NXLogStamp(__FILE__, __func__, __LINE__, l) + + +#define nxdbg nx_log << nxstamp(NXDEBUG) +#define nxinfo nx_log << nxstamp(NXINFO) +#define nxwarn nx_log << nxstamp(NXWARNING) +#define nxerr nx_log << nxstamp(NXERROR) +#define nxfatal nx_log << nxstamp(NXFATAL) + + +NXLog& operator<< (NXLog& out, const NXLogStamp& value); + + +template <typename T> +bool has_newline(T value) +{ + return false; +} + +template <char*> +static bool has_newline(char *value) +{ + return strstr(value, "\n") != NULL; +} + +template <char> +static bool has_newline(char value) +{ + return value == '\n'; +} + +template <std::string&> +static bool has_newline(std::string &value) +{ + return value.find_first_of("\n") != std::string::npos; +} + +static size_t ss_length(std::stringstream *ss) +{ + size_t pos = ss->tellg(); + size_t ret = 0; + ss->seekg(0, std::ios::end); + ret = ss->tellg(); + ss->seekg(pos, std::ios::beg); + return ret; +} + +template <typename T> +NXLog& operator<<(NXLog& out, const T& value) +{ + if ( out.will_log() ) + { + if ( out.synchronized() ) + { + // In synchronized mode, we buffer data until a newline, std::flush, or the buffer + // gets full. Then we dump the whole thing at once to the output stream, synchronizing + // with a mutex. + NXLog::per_thread_data *pdt = out.get_data(); + assert (!pdt->buffer.empty ()); + (*pdt->buffer.top()) << value; + + if ( ss_length(pdt->buffer.top()) >= out.thread_buffer_size_ || has_newline(value) ) + out.flush(); + + } + else + { + // In async mode we just dump data on the output stream as-is. + // Multithreaded code will have ugly output. + *(out.stream()) << value; + } + + } + + return out; +} + +#endif diff --git a/nxcomp/src/Loop.cpp b/nxcomp/src/Loop.cpp index 4292e7b9a..681a5a1ef 100644 --- a/nxcomp/src/Loop.cpp +++ b/nxcomp/src/Loop.cpp @@ -101,6 +101,7 @@ typedef int socklen_t; #include "Message.h" #include "ChannelEndPoint.h" +#include "Log.h" // // System specific defines. @@ -9339,6 +9340,102 @@ int ParseCommandLineOptions(int argc, const char **argv) return -1; } + case 'd': + { + if ( argi+1 >= argc ) + { + PrintUsageInfo(nextArg, 0); + return -1; + } + + int level = 0; + errno = 0; + level = strtol(argv[argi+1], NULL, 10); + + if ( errno && (level == 0) ) + { + cerr << "Warning: Failed to parse log level. Ignoring option." << std::endl; + } + if ( level < 0 ) + { + cerr << "Warning: Log level must be a positive integer. Ignoring option." << std::endl; + level = nx_log.level(); + } + else if ( level >= NXLOG_LEVEL_COUNT ) + { + cerr << "Warning: Log level is greater than the maximum " << NXLOG_LEVEL_COUNT-1 << ". Setting to the maximum." << std::endl; + level = NXLOG_LEVEL_COUNT-1; + } + + nx_log.level( (NXLogLevel)level ); + + argi++; + break; + + } + case 'o': + { + if ( argi + 1 >= argc ) + { + PrintUsageInfo(nextArg, 0); + return -1; + } + + std::ofstream *logfile = new std::ofstream(); + + // Unbuffered output + logfile->rdbuf()->pubsetbuf(0, 0); + logfile->open(argv[argi+1], std::ofstream::app); + + if ( logfile->is_open() ) + { + nx_log.stream(logfile); + } + else + { + cerr << "Failed to open log file " << argv[argi+1] << endl; + return -1; + } + + argi++; + break; + } + case 'f': + { + if ( argi + 1 >= argc ) + { + PrintUsageInfo(nextArg, 0); + return -1; + } + + const char *format = argv[argi+1]; + size_t pos = 0; + + nx_log.log_level(false); + nx_log.log_time(false); + nx_log.log_unix_time(false); + nx_log.log_location(false); + nx_log.log_thread_id(false); + + for(pos =0;pos<strlen(format);pos++) + { + switch(format[pos]) + { + case '0': break; + case 't': nx_log.log_time(true); break; + case 'u': nx_log.log_time(true); nx_log.log_unix_time(true); break; + case 'l': nx_log.log_level(true); break; + case 'T': nx_log.log_thread_id(true); break; + case 'L': nx_log.log_location(true); break; + default : cerr << "Unrecognized format specifier: " << format[pos] << endl; break; + } + } + + argi++; + break; + } + + default: { PrintUsageInfo(nextArg, 1); diff --git a/nxcomp/src/Makefile.am b/nxcomp/src/Makefile.am index 2264cb347..682ddbaca 100644 --- a/nxcomp/src/Makefile.am +++ b/nxcomp/src/Makefile.am @@ -114,12 +114,14 @@ libXcomp_la_SOURCES = \ WriteBuffer.cpp \ XidCache.cpp \ Z.cpp \ + Log.cpp \ $(NULL) libXcomp_la_LIBADD = \ @JPEG_LIBS@ \ @PNG_LIBS@ \ @Z_LIBS@ \ + @PTHREAD_LIBS@ \ $(NULL) AM_CXXFLAGS = \ @@ -127,13 +129,18 @@ AM_CXXFLAGS = \ $(JPEG_CFLAGS) \ $(PNG_CFLAGS) \ $(Z_CFLAGS) \ + $(PTHREAD_CFLAGS) \ $(NULL) AM_CPPFLAGS = \ -I$(top_srcdir)/include \ $(NULL) -libXcomp_la_LDFLAGS = -version-number @LT_COMP_VERSION@ -no-undefined +libXcomp_la_LDFLAGS = \ + -version-number @LT_COMP_VERSION@ \ + -no-undefined \ + $(PTHREAD_LDFLAGS) \ + $(NULL) libXcompincludedir = $(includedir)/nx libXcompinclude_HEADERS = \ diff --git a/nxcomp/test/Makefile.am b/nxcomp/test/Makefile.am new file mode 100644 index 000000000..412b8ea94 --- /dev/null +++ b/nxcomp/test/Makefile.am @@ -0,0 +1,22 @@ +NULL = + +noinst_PROGRAMS = logging_test +EXTRA_DIST = logging_test + +AM_CPPFLAGS = -I$(top_srcdir)/src +AM_CXXFLAGS = \ + @PTHREAD_CFLAGS@ \ + $(NULL) + +logging_test_SOURCES = logging_test.cpp +logging_test_LDADD = \ + $(top_srcdir)/src/.libs/libXcomp.a \ + @PTHREAD_LIBS@ \ + $(NULL) + +logging_test_LDFLAGS = \ + $(PTHREAD_LDFLAGS) \ + $(NULL) + +check: all + ./logging_test diff --git a/nxcomp/test/logging_test.cpp b/nxcomp/test/logging_test.cpp new file mode 100644 index 000000000..7e2d7d213 --- /dev/null +++ b/nxcomp/test/logging_test.cpp @@ -0,0 +1,224 @@ +#include <cstddef> +#include <pthread.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <cstdlib> +#include <ctime> +#include <climits> +#include <vector> +#include <cstring> + +#include "logging_test.h" + +Faulty_Logger faulty_logger; +NXLog good_logger; + +void print_sigmask () { + sigset_t orig_mask; + sigemptyset (&orig_mask); + + pthread_sigmask (SIG_SETMASK, NULL, &orig_mask); + + bool empty = true; + for (std::size_t i = 0; i < NSIG; ++i) { + if (sigismember (&orig_mask, i)) { + nxdbg_good << "Signal i (" << i << ") in signal mask." << std::endl; + empty = false; + } + } + + if (empty) { + nxdbg_good << "Signal mask empty."; + } +} + +void* log_task (void* /* unused */) { + /* print_sigmask (); */ + + for (std::size_t i = 0; i < 10; ++i) { + nxinfo << "Log message " << i << std::endl; + } +} + +void sig_handler (int signo) { + nxinfo << "Received signal " << signo << std::endl; +} + +void setup_faulty_logger () { + faulty_logger.log_level (true); + faulty_logger.log_unix_time (false); + faulty_logger.log_time (true); + faulty_logger.log_location (true); + faulty_logger.log_thread_id (true); + faulty_logger.level (NXDEBUG); +} + +void setup_good_logger () { + good_logger.log_level (true); + good_logger.log_unix_time (false); + good_logger.log_time (true); + good_logger.log_location (true); + good_logger.log_thread_id (true); + good_logger.level (NXDEBUG); +} + +pthread_t spawn_thread () { + pthread_t thread_id; + int pthread_ret; + + sigset_t block_mask, orig_mask; + sigemptyset (&orig_mask); + sigfillset (&block_mask); + + pthread_sigmask (SIG_BLOCK, &block_mask, &orig_mask); + + pthread_ret = pthread_create (&thread_id, NULL, log_task, NULL); + + pthread_sigmask (SIG_SETMASK, &orig_mask, NULL); + + return (thread_id); +} + +void install_signal_handler () { + struct sigaction sa; + sa.sa_handler = sig_handler; + sigemptyset (&sa.sa_mask); + sa.sa_flags = SA_RESTART; + + if (-1 == sigaction (SIGUSR1, &sa, NULL)) { + nxerr_good << "Unable to install signal handler!" << std::endl; + } + else { + nxdbg_good << "Signal handler registered successfully for SIGUSR1." << std::endl; + } +} + +void killing_process_work (pid_t parent_pid) { + /* Seed PRNG. */ + std::srand (std::time (0)); + + for (std::size_t i = 0; i < 25; ++i) { + /* Sleep for 4 seconds + some random number up to a second. */ + std::size_t rand_add = (std::rand () % 1000000); + usleep (4000000 + rand_add); + + /* Send SIGUSR1 to parent process. */ + nxdbg_good << "Sending SIGUSR1 (" << SIGUSR1 << ") to parent_pid (" << parent_pid << ")" << std::endl; + + if (kill (parent_pid, SIGUSR1)) { + int saved_errno = errno; + nxerr_good << "Failed to deliver signal to parent, aborting." << std::endl; + nxerr_good << "Error " << saved_errno << ": " << strerror (saved_errno) << std::endl; + exit (EXIT_FAILURE); + } + } + + exit (EXIT_SUCCESS); +} + +void killing_process_init (int argc, char **argv) { + /* We're in the "killing process". */ + pid_t parent_pid = getppid (); + + setup_good_logger (); + + for (std::size_t i = 0; i < argc; ++i) { + nxdbg_good << "argv[" << i << "]: " << argv[i] << std::endl; + } + + char *end = NULL; + + errno = 0; + long parent_pid_check = std::strtol (argv[1], &end, 0); + + if ((errno == ERANGE) && (parent_pid_check == LONG_MAX)) { + /* Overflow, handle gracefully. */ + parent_pid_check = 1; + } + + if ((errno == ERANGE) && (parent_pid_check == LONG_MIN)) { + /* Underflow, handle gracefully. */ + parent_pid_check = 1; + } + + if (*end) { + /* Conversion error (for inputs like "<number>X", end will point to X.) */ + parent_pid_check = 1; + } + + if (parent_pid != parent_pid_check) { + nxinfo_good << "Parent PID verification via first argument failed, trusting getppid ()." << std::endl; + } + + killing_process_work (parent_pid); +} + +int main (int argc, char **argv) { + if (argc > 1) { + killing_process_init (argc, argv); + } + else { + /* That's the main process. */ + + /* First, fork and create the "killing process". */ + pid_t pid = fork (); + + if (0 == pid) { + /* Child process. */ + pid_t parent_pid = getppid (); + + /* Prepare to pass-through parent PID. */ + std::stringstream ss; + ss << parent_pid; + + std::vector<std::string> new_argv; + new_argv.push_back (std::string (argv[0])); + new_argv.push_back (ss.str ()); + + std::vector<char *> new_argv_c_str; + for (std::vector<std::string>::iterator it = new_argv.begin (); it != new_argv.end (); ++it) { + const char *elem = (*it).c_str (); + new_argv_c_str.push_back (strndup (elem, std::strlen (elem))); + } + + /* Add null pointer as last element. */ + new_argv_c_str.push_back (0); + + /* Relaunch, with argv[1] containing the ppid. */ + if (0 != execvp (new_argv_c_str.front (), &(new_argv_c_str.front ()))) { + const int saved_errno = errno; + std::cerr << "Failed to start \"killing process\"! Panic!" << std::endl; + std::cerr << "System error: " << std::strerror (saved_errno) << std::endl; + exit (EXIT_FAILURE); + } + } + else if (0 > pid) { + const int saved_errno = errno; + std::cerr << "Error while forking main process! Panic!" << std::endl; + std::cerr << "System error: " << std::strerror (saved_errno) << std::endl; + exit (EXIT_FAILURE); + } + else { + /* Main process. */ + /* Falls through to general code below. */ + } + } + + setup_faulty_logger (); + + pthread_t thread_id = spawn_thread (); + + setup_good_logger (); + + install_signal_handler (); + + /* print_sigmask (); */ + + log_task (NULL); + + pthread_join (thread_id, NULL); + + exit (EXIT_SUCCESS); +} diff --git a/nxcomp/test/logging_test.h b/nxcomp/test/logging_test.h new file mode 100644 index 000000000..239fbfe2b --- /dev/null +++ b/nxcomp/test/logging_test.h @@ -0,0 +1,121 @@ +#ifndef LOGGING_TEST_H +#define LOGGING_TEST_H + +#include <unistd.h> + +#define INTERNAL_LOGGING_TEST +#include "Log.h" + +class Faulty_Logger : public NXLog { + /* Copied from base class, inserted "fault" within critical section. */ + using NXLog::flush; + void flush(per_thread_data *pdt) + { + sigset_t orig_signal_mask, + tmp_signal_mask; + sigemptyset(&orig_signal_mask); + + sigfillset(&tmp_signal_mask); + + pthread_sigmask(SIG_BLOCK, &tmp_signal_mask, &orig_signal_mask); + + if (!pdt->buffer.empty ()) { + const std::string str = pdt->buffer.top()->str(); + + if (!str.empty()) + { + pthread_mutex_lock(&output_lock_); + usleep (3000000); + (*stream()) << str; + pthread_mutex_unlock(&output_lock_); + } + + pdt->buffer.pop(); + } + + pthread_sigmask(SIG_SETMASK, &orig_signal_mask, NULL); + } + + template<typename T> + friend Faulty_Logger& operator<<(Faulty_Logger& out, const T& value); + + friend Faulty_Logger& operator<< (Faulty_Logger& out, const NXLogStamp& value); +}; + +template <typename T> +Faulty_Logger& operator<<(Faulty_Logger& out, const T& value) { + if ( out.will_log() ) { + if ( out.synchronized() ) { + // In synchronized mode, we buffer data until a newline, std::flush, or the buffer + // gets full. Then we dump the whole thing at once to the output stream, synchronizing + // with a mutex. + Faulty_Logger::per_thread_data *pdt = out.get_data(); + assert (!pdt->buffer.empty ()); + usleep (1000000); + (*pdt->buffer.top()) << value; + + if ( ss_length(pdt->buffer.top()) >= out.thread_buffer_size_ || has_newline(value) ) + out.flush(); + } + else { + // In async mode we just dump data on the output stream as-is. + // Multithreaded code will have ugly output. + *(out.stream()) << value; + } + } + + return out; +} + +Faulty_Logger& operator<< (Faulty_Logger& out, const NXLogStamp& value) +{ + out.current_level( value.level() ); + out.current_file( value.file() ); + + // Writing an NXLogStamp to the stream indicates the start of a new entry. + // If there's any content in the buffer, create a new entry in the output + // queue. + if ( out.synchronized() ) + out.new_stack_entry(); + + out << out.stamp_to_string(value); + + return out; +} + +#undef nxdbg +#undef nxinfo +#undef nxwarn +#undef nxerr +#undef nxfatal + +#define nxdbg faulty_logger << nxstamp(NXDEBUG) +#define nxinfo faulty_logger << nxstamp(NXINFO) +#define nxwarn faulty_logger << nxstamp(NXWARNING) +#define nxerr faulty_logger << nxstamp(NXERROR) +#define nxfatal faulty_logger << nxstamp(NXFATAL) + +#define nxdbg_good good_logger << nxstamp(NXDEBUG) +#define nxinfo_good good_logger << nxstamp(NXINFO) +#define nxwarn_good good_logger << nxstamp(NXWARNING) +#define nxerr_good good_logger << nxstamp(NXERROR) +#define nxfatal_good good_logger << nxstamp(NXFATAL) + +/* Helper functions used by all component. */ +void print_sigmask (); +void setup_faulty_logger (); +void setup_good_logger (); + +/* Functions used by both main and auxiliary threads. */ +void* log_task (void* /* unused */); + +/* Functions used in main thread only. */ +pthread_t spawn_thread (); +void install_signal_handler (); +void sig_handler (int signo); + +/* Functions used by "killing" process. */ +void killing_process_init (int argc, char **argv); +void killing_process_work (pid_t parent_pid); + +#endif /* !defined (LOGGING_TEST_H) */ |