--- /dev/null
+Reinhard Pfau <reinhard.pfau@intra2net.com>
--- /dev/null
+AUTOMAKE_OPTIONS = foreign
+
+SUBDIRS = doc simpleio unittest
--- /dev/null
+dnl @synopsis AX_BOOST([MINIMUM-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
+dnl
+dnl Test for the Boost C++ libraries of a particular version (or newer)
+dnl
+dnl If no path to the installed boost library is given the macro
+dnl searchs under /usr, /usr/local, and /opt, and evaluates the
+dnl $BOOST_ROOT environment variable. Further documentation is
+dnl available at <http://randspringer.de/boost/index.html>.
+dnl
+dnl This macro calls:
+dnl
+dnl AC_SUBST(BOOST_CPPFLAGS) / AC_SUBST(BOOST_LDFLAGS)
+dnl AC_SUBST(BOOST_FILESYSTEM_LIB)
+dnl AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB)
+dnl AC_SUBST(BOOST_THREAD_LIB)
+dnl AC_SUBST(BOOST_IOSTREAMS_LIB)
+dnl AC_SUBST(BOOST_SERIALIZATION_LIB)
+dnl AC_SUBST(BOOST_WSERIALIZATION_LIB)
+dnl AC_SUBST(BOOST_SIGNALS_LIB)
+dnl AC_SUBST(BOOST_DATE_TIME_LIB)
+dnl AC_SUBST(BOOST_REGEX_LIB)
+dnl AC_SUBST(BOOST_UNIT_TEST_FRAMEWORK_LIB)
+dnl
+dnl And sets:
+dnl
+dnl HAVE_BOOST
+dnl HAVE_BOOST_FILESYSTEM
+dnl HAVE_BOOST_PROGRAM_OPTIONS
+dnl HAVE_BOOST_THREAD
+dnl HAVE_BOOST_IOSTREAMS
+dnl HAVE_BOOST_SERIALIZATION
+dnl HAVE_BOOST_SIGNALS
+dnl HAVE_BOOST_DATE_TIME
+dnl HAVE_BOOST_REGEX
+dnl HAVE_BOOST_UNIT_TEST_FRAMEWORK
+dnl
+dnl @category InstalledPackages
+dnl @category Cxx
+dnl @author Thomas Porschberg <thomas@randspringer.de>
+dnl @version 2006-06-15
+dnl @license AllPermissive
+
+AC_DEFUN([AX_BOOST],
+[
+ AC_ARG_WITH([boost],,
+ [
+ if test "$withval" = "no"; then
+ want_boost="no"
+ elif test "$withval" = "yes"; then
+ want_boost="yes"
+ ac_boost_path=""
+ else
+ want_boost="yes"
+ ac_boost_path="$withval"
+ fi
+ ],
+ [want_boost="yes"])
+
+ AC_CANONICAL_BUILD
+ if test "x$want_boost" = "xyes"; then
+ AC_REQUIRE([AC_PROG_CC])
+ boost_lib_version_req=ifelse([$1], ,1.20.0,$1)
+ boost_lib_version_req_shorten=`expr $boost_lib_version_req : '\([[0-9]]*\.[[0-9]]*\)'`
+ boost_lib_version_req_major=`expr $boost_lib_version_req : '\([[0-9]]*\)'`
+ boost_lib_version_req_minor=`expr $boost_lib_version_req : '[[0-9]]*\.\([[0-9]]*\)'`
+ boost_lib_version_req_sub_minor=`expr $boost_lib_version_req : '[[0-9]]*\.[[0-9]]*\.\([[0-9]]*\)'`
+ if test "x$boost_lib_version_req_sub_minor" = "x" ; then
+ boost_lib_version_req_sub_minor="0"
+ fi
+ WANT_BOOST_VERSION=`expr $boost_lib_version_req_major \* 100000 \+ $boost_lib_version_req_minor \* 100 \+ $boost_lib_version_req_sub_minor`
+ AC_MSG_CHECKING(for boostlib >= $boost_lib_version_req)
+ succeeded=no
+
+ dnl first we check the system location for boost libraries
+ dnl this location ist chosen if boost libraries are installed with the --layout=system option
+ dnl or if you install boost with RPM
+ if test "$ac_boost_path" != ""; then
+ BOOST_LDFLAGS="-L$ac_boost_path/lib"
+ BOOST_CPPFLAGS="-I$ac_boost_path/include"
+ else
+ for ac_boost_path_tmp in /usr /usr/local /opt ; do
+ if test -d "$ac_boost_path_tmp/include/boost" && test -r "$ac_boost_path_tmp/include/boost"; then
+ BOOST_LDFLAGS="-L$ac_boost_path_tmp/lib"
+ BOOST_CPPFLAGS="-I$ac_boost_path_tmp/include"
+ break;
+ fi
+ done
+ fi
+
+ CPPFLAGS_SAVED="$CPPFLAGS"
+ CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+ export CPPFLAGS
+
+ LDFLAGS_SAVED="$LDFLAGS"
+ LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+ export LDFLAGS
+
+ AC_LANG_PUSH(C++)
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+@%:@include <boost/version.hpp>
+]],
+ [[
+#if BOOST_VERSION >= $WANT_BOOST_VERSION
+// Everything is okay
+#else
+# error Boost version is too old
+#endif
+
+ ]])],
+ [
+ AC_MSG_RESULT(yes)
+ succeeded=yes
+ found_system=yes
+ ifelse([$2], , :, [$2])
+ ],
+ [
+ ])
+ AC_LANG_POP([C++])
+ dnl if we found no boost with system layout we search for boost libraries
+ dnl built and installed without the --layout=system option or for a staged(not installed) version
+ if test "x$succeeded" != "xyes"; then
+ _version=0
+ if test "$ac_boost_path" != ""; then
+ BOOST_LDFLAGS="-L$ac_boost_path/lib"
+ if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+ for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+ _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+ V_CHECK=`expr $_version_tmp \> $_version`
+ if test "$V_CHECK" = "1" ; then
+ _version=$_version_tmp
+ fi
+ VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+ BOOST_CPPFLAGS="-I$ac_boost_path/include/boost-$VERSION_UNDERSCORE"
+ done
+ fi
+ else
+ for ac_boost_path in /usr /usr/local /opt ; do
+ if test -d "$ac_boost_path" && test -r "$ac_boost_path"; then
+ for i in `ls -d $ac_boost_path/include/boost-* 2>/dev/null`; do
+ _version_tmp=`echo $i | sed "s#$ac_boost_path##" | sed 's/\/include\/boost-//' | sed 's/_/./'`
+ V_CHECK=`expr $_version_tmp \> $_version`
+ if test "$V_CHECK" = "1" ; then
+ _version=$_version_tmp
+ best_path=$ac_boost_path
+ fi
+ done
+ fi
+ done
+
+ VERSION_UNDERSCORE=`echo $_version | sed 's/\./_/'`
+ BOOST_CPPFLAGS="-I$best_path/include/boost-$VERSION_UNDERSCORE"
+ BOOST_LDFLAGS="-L$best_path/lib"
+
+ if test "x$BOOST_ROOT" != "x"; then
+ if test -d "$BOOST_ROOT" && test -r "$BOOST_ROOT" && test -d "$BOOST_ROOT/stage/lib" && test -r "$BOOST_ROOT/stage/lib"; then
+ version_dir=`expr //$BOOST_ROOT : '.*/\(.*\)'`
+ stage_version=`echo $version_dir | sed 's/boost_//' | sed 's/_/./g'`
+ stage_version_shorten=`expr $stage_version : '\([[0-9]]*\.[[0-9]]*\)'`
+ V_CHECK=`expr $stage_version_shorten \>\= $_version`
+ if test "$V_CHECK" = "1" ; then
+ AC_MSG_NOTICE(We will use a staged boost library from $BOOST_ROOT)
+ BOOST_CPPFLAGS="-I$BOOST_ROOT"
+ BOOST_LDFLAGS="-L$BOOST_ROOT/stage/lib"
+ fi
+ fi
+ fi
+ fi
+
+ CPPFLAGS="$CPPFLAGS $BOOST_CPPFLAGS"
+ export CPPFLAGS
+ LDFLAGS="$LDFLAGS $BOOST_LDFLAGS"
+ export LDFLAGS
+
+ AC_LANG_PUSH(C++)
+ AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
+@%:@include <boost/version.hpp>
+]],
+ [[
+#if BOOST_VERSION >= $WANT_BOOST_VERSION
+// Everything is okay
+#else
+# error Boost version is too old
+#endif
+
+ ]])],
+ [
+ AC_MSG_RESULT(yes ($_version))
+ succeeded=yes
+ ifelse([$2], , :, [$2])
+ ],
+ [
+ AC_MSG_RESULT(no ($_version))
+ ifelse([$3], , :, [$3])
+ ])
+ AC_LANG_POP([C++])
+ fi
+
+ if test "$succeeded" != "yes" ; then
+ if test "$_version" = "0" ; then
+ AC_MSG_ERROR([[We could not detect the boost libraries (version $boost_lib_version_req_shorten or higher). If you have a staged boost library (still not installed) please specify \$BOOST_ROOT in your environment and do not give a PATH to --with-boost option. If you are sure you have boost installed, then check your version number looking in <boost/version.hpp>. See http://randspringer.de/boost for more documentation.]])
+ else
+ AC_MSG_ERROR('Your boost libraries seems to old (version $_version). We need at least $boost_lib_version_shorten')
+ fi
+ else
+ AC_SUBST(BOOST_CPPFLAGS)
+ AC_SUBST(BOOST_LDFLAGS)
+ AC_DEFINE(HAVE_BOOST,,[define if the Boost library is available])
+
+ AC_CACHE_CHECK([whether the Boost::Filesystem library is available],
+ ax_cv_boost_filesystem,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/filesystem/path.hpp>]],
+ [[using namespace boost::filesystem;
+ path my_path( "foo/bar/data.txt" );
+ return 0;]]),
+ ax_cv_boost_filesystem=yes, ax_cv_boost_filesystem=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "$ax_cv_boost_filesystem" = "yes"; then
+ AC_DEFINE(HAVE_BOOST_FILESYSTEM,,[define if the Boost::FILESYSTEM library is available])
+ BN=boost_filesystem
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main,
+ [BOOST_FILESYSTEM_LIB="-l$ax_lib" AC_SUBST(BOOST_FILESYSTEM_LIB) link_filesystem="yes" break],
+ [link_filesystem="no"])
+ done
+ if test "x$link_filesystem" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK([whether the Boost::Program_Options library is available],
+ ax_cv_boost_program_options,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/program_options.hpp>]],
+ [[boost::program_options::options_description generic("Generic options");
+ return 0;]]),
+ ax_cv_boost_program_options=yes, ax_cv_boost_program_options=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "$ax_cv_boost_program_options" = yes; then
+ AC_DEFINE(HAVE_BOOST_PROGRAM_OPTIONS,,[define if the Boost::PROGRAM_OPTIONS library is available])
+ BN=boost_program_options
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main,
+ [BOOST_PROGRAM_OPTIONS_LIB="-l$ax_lib" AC_SUBST(BOOST_PROGRAM_OPTIONS_LIB) link_program_options="yes" break],
+ [link_program_options="no"])
+ done
+ if test "x$link_program_options="no"" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::Thread library is available,
+ ax_cv_boost_thread,
+ [AC_LANG_PUSH([C++])
+ CXXFLAGS_SAVE=$CXXFLAGS
+
+ if test "x$build_os" = "xsolaris" ; then
+ CXXFLAGS="-pthreads $CXXFLAGS"
+ elif test "x$build_os" = "xming32" ; then
+ CXXFLAGS="-mthreads $CXXFLAGS"
+ else
+ CXXFLAGS="-pthread $CXXFLAGS"
+ fi
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/thread/thread.hpp>]],
+ [[boost::thread_group thrds;
+ return 0;]]),
+ ax_cv_boost_thread=yes, ax_cv_boost_thread=no)
+ CXXFLAGS=$CXXFLAGS_SAVE
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_thread" = "xyes"; then
+ if test "x$build_os" = "xsolaris" ; then
+ BOOST_CPPFLAGS="-pthreads $BOOST_CPPFLAGS"
+ elif test "x$build_os" = "xming32" ; then
+ BOOST_CPPFLAGS="-mthreads $BOOST_CPPFLAGS"
+ else
+ BOOST_CPPFLAGS="-pthread $BOOST_CPPFLAGS"
+ fi
+
+ AC_SUBST(BOOST_CPPFLAGS)
+ AC_DEFINE(HAVE_BOOST_THREAD,,[define if the Boost::THREAD library is available])
+ BN=boost_thread
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main, [BOOST_THREAD_LIB="-l$ax_lib" AC_SUBST(BOOST_THREAD_LIB) link_thread="yes" break],
+ [link_thread="no"])
+ done
+ if test "x$link_thread" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::IOStreams library is available,
+ ax_cv_boost_iostreams,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/iostreams/filtering_stream.hpp>
+ @%:@include <boost/range/iterator_range.hpp>
+ ]],
+ [[std::string input = "Hello World!";
+ namespace io = boost::iostreams;
+ io::filtering_istream in(boost::make_iterator_range(input));
+ return 0;
+ ]]),
+ ax_cv_boost_iostreams=yes, ax_cv_boost_iostreams=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_iostreams" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_IOSTREAMS,,[define if the Boost::IOStreams library is available])
+ BN=boost_iostreams
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main, [BOOST_IOSTREAMS_LIB="-l$ax_lib" AC_SUBST(BOOST_IOSTREAMS_LIB) link_thread="yes" break],
+ [link_thread="no"])
+ done
+ if test "x$link_thread" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::Serialization library is available,
+ ax_cv_boost_serialization,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <fstream>
+ @%:@include <boost/archive/text_oarchive.hpp>
+ @%:@include <boost/archive/text_iarchive.hpp>
+ ]],
+ [[std::ofstream ofs("filename");
+ boost::archive::text_oarchive oa(ofs);
+ return 0;
+ ]]),
+ ax_cv_boost_serialization=yes, ax_cv_boost_serialization=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_serialization" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_SERIALIZATION,,[define if the Boost::Serialization library is available])
+ BN=boost_serialization
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main,
+ [BOOST_SERIALIZATION_LIB="-l$ax_lib" AC_SUBST(BOOST_SERIALIZATION_LIB) link_serialization="yes" break],
+ [link_serialization="no"])
+ done
+ if test "x$link_serialization" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+
+ BN=boost_wserialization
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main,
+ [BOOST_WSERIALIZATION_LIB="-l$ax_lib" AC_SUBST(BOOST_WSERIALIZATION_LIB) link_wserialization="yes" break],
+ [link_wserialization="no"])
+ done
+ if test "x$link_wserialization" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::Signals library is available,
+ ax_cv_boost_signals,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/signal.hpp>
+ ]],
+ [[boost::signal<void ()> sig;
+ return 0;
+ ]]),
+ ax_cv_boost_signals=yes, ax_cv_boost_signals=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_signals" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_SIGNALS,,[define if the Boost::Signals library is available])
+ BN=boost_signals
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main, [BOOST_SIGNALS_LIB="-l$ax_lib" AC_SUBST(BOOST_SIGNALS_LIB) link_signals="yes" break],
+ [link_signals="no"])
+ done
+ if test "x$link_signals" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::Date_Time library is available,
+ ax_cv_boost_date_time,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/date_time/gregorian/gregorian_types.hpp>
+ ]],
+ [[using namespace boost::gregorian; date d(2002,Jan,10);
+ return 0;
+ ]]),
+ ax_cv_boost_date_time=yes, ax_cv_boost_date_time=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_date_time" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_DATE_TIME,,[define if the Boost::Date_Time library is available])
+ BN=boost_date_time
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main, [BOOST_DATE_TIME_LIB="-l$ax_lib" AC_SUBST(BOOST_DATE_TIME_LIB) link_thread="yes" break],
+ [link_thread="no"])
+ done
+ if test "x$link_thread"="no" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::Regex library is available,
+ ax_cv_boost_regex,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/regex.hpp>
+ ]],
+ [[boost::regex r(); return 0;]]),
+ ax_cv_boost_regex=yes, ax_cv_boost_regex=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_regex" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_REGEX,,[define if the Boost::Regex library is available])
+ BN=boost_regex
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ AC_CHECK_LIB($ax_lib, main, [BOOST_REGEX_LIB="-l$ax_lib" AC_SUBST(BOOST_REGEX_LIB) link_regex="yes" break],
+ [link_regex="no"])
+ done
+ if test "x$link_regex" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+
+ AC_CACHE_CHECK(whether the Boost::UnitTestFramework library is available,
+ ax_cv_boost_unit_test_framework,
+ [AC_LANG_PUSH([C++])
+ AC_COMPILE_IFELSE(AC_LANG_PROGRAM([[@%:@include <boost/test/unit_test.hpp>]],
+ [[using boost::unit_test::test_suite;
+ test_suite* test= BOOST_TEST_SUITE( "Unit test example 1" ); return 0;]]),
+ ax_cv_boost_unit_test_framework=yes, ax_cv_boost_unit_test_framework=no)
+ AC_LANG_POP([C++])
+ ])
+ if test "x$ax_cv_boost_unit_test_framework" = "xyes"; then
+ AC_DEFINE(HAVE_BOOST_UNIT_TEST_FRAMEWORK,,[define if the Boost::Unit_test_framework library is available])
+ BN=boost_unit_test_framework
+ saved_ldflags="${LDFLAGS}"
+ for ax_lib in $BN $BN-$CC $BN-$CC-mt $BN-$CC-mt-s $BN-$CC-s \
+ lib$BN lib$BN-$CC lib$BN-$CC-mt lib$BN-$CC-mt-s lib$BN-$CC-s \
+ $BN-mgw $BN-mgw $BN-mgw-mt $BN-mgw-mt-s $BN-mgw-s ; do
+ LDFLAGS="${LDFLAGS} -l$ax_lib"
+ AC_CACHE_CHECK(the name of the Boost::UnitTestFramework library,
+ ax_cv_boost_unit_test_framework_link,
+ [AC_LANG_PUSH([C++])
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([[@%:@include <boost/test/unit_test.hpp>
+ using boost::unit_test::test_suite;
+ test_suite* init_unit_test_suite( int argc, char * argv[] ) {
+ test_suite* test= BOOST_TEST_SUITE( "Unit test example 1" );
+ return test;
+ }
+ ]],
+ [[ return 0;]])],
+ link_unit_test_framework="yes",link_unit_test_framework="no")
+ AC_LANG_POP([C++])
+ ])
+ LDFLAGS="${saved_ldflags}"
+ if test "x$link_unit_test_framework" = "xyes"; then
+ BOOST_UNIT_TEST_FRAMEWORK_LIB="-l$ax_lib"
+ AC_SUBST(BOOST_UNIT_TEST_FRAMEWORK_LIB)
+ break
+ fi
+ done
+ if test "x$link_unit_test_framework" = "xno"; then
+ AC_MSG_NOTICE(Could not link against $ax_lib !)
+ fi
+ fi
+ fi
+ CPPFLAGS="$CPPFLAGS_SAVED"
+ LDFLAGS="$LDFLAGS_SAVED"
+ fi
+])
+
--- /dev/null
+AC_INIT(configure.in)
+
+AM_CONFIG_HEADER(config.h)
+AM_INIT_AUTOMAKE(libsimpleio, 0.1)
+
+LIBSIMPLEIO_LIB_VERSION=0:0:0
+
+AC_SUBST(LIBSIMPLEIO_LIB_VERSION)
+
+AC_LANG_CPLUSPLUS
+AC_PROG_CXX
+AM_PROG_LIBTOOL
+
+
+AC_ARG_WITH(optimize,[ --with-optimize compile with optimizing],
+[
+ AC_MSG_CHECKING(for optimizing)
+ if test "$withval" != "no"; then
+ CXXFLAGS=" -O2 "
+ AC_MSG_RESULT(yes)
+ else
+ CXXFLAGS=" -g -O0 "
+ AC_MSG_RESULT(no)
+ fi
+],[ CXXFLAGS=" -g -O0 "])
+
+dnl check for doxygen
+AC_PATH_PROG(DOXYGEN, doxygen)
+AM_CONDITIONAL(HAVE_DOXYGEN, test -n $DOXYGEN)
+
+dnl check for libraries:
+
+PKG_CHECK_MODULES(LIBI2NCOMMON, libi2ncommon)
+AC_SUBST(LIBI2NCOMMON_CFLAGS)
+AC_SUBST(LIBI2NCOMMON_LIBS)
+
+
+AM_PATH_CPPUNIT(1.8.0)
+
+AX_BOOST([1.34])
+if test "x$BOOST_SIGNALS_LIB" = "x"; then
+ echo "Sorry, we need the Signals-Lib from Boost."
+ exit 1
+fi
+
+
+dnl
+dnl spit out the result files:
+
+
+AC_OUTPUT(Makefile doc/Makefile simpleio/Makefile unittest/Makefile
+ simpleio/libsimpleio.pc
+)
--- /dev/null
+INCLUDES =
+METASOURCES = AUTO
--- /dev/null
+<?xml version = '1.0'?>
+<kdevelop>
+ <general>
+ <author>Reinhard Pfau</author>
+ <email>reinhard.pfau@intra2net.com</email>
+ <version>0.1</version>
+ <projectmanagement>KDevAutoProject</projectmanagement>
+ <primarylanguage>C++</primarylanguage>
+ <keywords>
+ <keyword>C++</keyword>
+ <keyword>Code</keyword>
+ </keywords>
+ <versioncontrol>kdevsubversion</versioncontrol>
+ <ignoreparts/>
+ <projectname>libsimpleio</projectname>
+ </general>
+ <kdevautoproject>
+ <general>
+ <activetarget>simpleio/libsimpleio.la</activetarget>
+ </general>
+ <run>
+ <runarguments>
+ <testconnd/>
+ </runarguments>
+ <debugarguments>
+ <testconnd/>
+ </debugarguments>
+ <cwd>
+ <testconnd>/home/reinhard/source/source/libsimpleio/./</testconnd>
+ </cwd>
+ </run>
+ <configurations>
+ <optimized>
+ <builddir>optimized</builddir>
+ <ccompiler>kdevgccoptions</ccompiler>
+ <cxxcompiler>kdevgppoptions</cxxcompiler>
+ <f77compiler>kdevg77options</f77compiler>
+ <cxxflags>-O2 -g0</cxxflags>
+ </optimized>
+ <debug>
+ <configargs>--enable-debug=full</configargs>
+ <builddir>debug</builddir>
+ <ccompiler>kdevgccoptions</ccompiler>
+ <cxxcompiler>kdevgppoptions</cxxcompiler>
+ <f77compiler>kdevg77options</f77compiler>
+ <cxxflags>-O0 -g3</cxxflags>
+ </debug>
+ </configurations>
+ <make>
+ <envvars>
+ <envvar value="1" name="WANT_AUTOCONF_2_5" />
+ <envvar value="1" name="WANT_AUTOMAKE_1_6" />
+ </envvars>
+ </make>
+ </kdevautoproject>
+ <kdevdoctreeview>
+ <ignoretocs>
+ <toc>ada</toc>
+ <toc>ada_bugs_gcc</toc>
+ <toc>bash</toc>
+ <toc>bash_bugs</toc>
+ <toc>clanlib</toc>
+ <toc>w3c-dom-level2-html</toc>
+ <toc>fortran_bugs_gcc</toc>
+ <toc>gnome1</toc>
+ <toc>gnustep</toc>
+ <toc>gtk</toc>
+ <toc>gtk_bugs</toc>
+ <toc>haskell</toc>
+ <toc>haskell_bugs_ghc</toc>
+ <toc>java_bugs_gcc</toc>
+ <toc>java_bugs_sun</toc>
+ <toc>kde2book</toc>
+ <toc>opengl</toc>
+ <toc>pascal_bugs_fp</toc>
+ <toc>php</toc>
+ <toc>php_bugs</toc>
+ <toc>perl</toc>
+ <toc>perl_bugs</toc>
+ <toc>python</toc>
+ <toc>python_bugs</toc>
+ <toc>qt-kdev3</toc>
+ <toc>ruby</toc>
+ <toc>ruby_bugs</toc>
+ <toc>sdl</toc>
+ <toc>w3c-svg</toc>
+ <toc>sw</toc>
+ <toc>w3c-uaag10</toc>
+ <toc>wxwidgets_bugs</toc>
+ </ignoretocs>
+ <ignoreqt_xml>
+ <toc>Guide to the Qt Translation Tools</toc>
+ <toc>Qt Assistant Manual</toc>
+ <toc>Qt Designer Manual</toc>
+ <toc>Qt Reference Documentation</toc>
+ <toc>qmake User Guide</toc>
+ </ignoreqt_xml>
+ <ignoredoxygen>
+ <toc>KDE Libraries (Doxygen)</toc>
+ </ignoredoxygen>
+ </kdevdoctreeview>
+ <kdevfilecreate>
+ <useglobaltypes>
+ <type ext="cpp" />
+ <type ext="h" />
+ </useglobaltypes>
+ </kdevfilecreate>
+ <kdevfileview>
+ <groups>
+ <group pattern="*.h" name="Header files" />
+ <group pattern="*.cpp" name="Source files" />
+ <hidenonprojectfiles>false</hidenonprojectfiles>
+ <hidenonlocation>false</hidenonlocation>
+ </groups>
+ <tree>
+ <hidepatterns>*.o,*.lo,CVS</hidepatterns>
+ <hidenonprojectfiles>false</hidenonprojectfiles>
+ <showvcsfields>false</showvcsfields>
+ </tree>
+ </kdevfileview>
+ <kdevdocumentation>
+ <projectdoc>
+ <docsystem>Doxygen Documentation Collection</docsystem>
+ <docurl>libsimpleio.tag</docurl>
+ </projectdoc>
+ </kdevdocumentation>
+ <substmap>
+ <APPNAME>libsimpleio</APPNAME>
+ <APPNAMELC>libsimpleio</APPNAMELC>
+ <APPNAMESC>Libsimpleio</APPNAMESC>
+ <APPNAMEUC>LIBSIMPLEIO</APPNAMEUC>
+ <AUTHOR>Reinhard Pfau</AUTHOR>
+ <EMAIL>reinhard.pfau@intra2net.com</EMAIL>
+ <LICENSE>Custom</LICENSE>
+ <VERSION>0.1</VERSION>
+ <YEAR>2008</YEAR>
+ <dest>/home/reinhard/source/source/libsimpleio</dest>
+ </substmap>
+ <kdevcppsupport>
+ <qt>
+ <used>false</used>
+ <version>3</version>
+ <includestyle>3</includestyle>
+ <root>/usr/lib/qt-3.3</root>
+ <designerintegration>EmbeddedKDevDesigner</designerintegration>
+ <qmake>/usr/lib/qt-3.3/bin/qmake</qmake>
+ <designer>/usr/lib/qt-3.3/bin/designer</designer>
+ <designerpluginpaths/>
+ </qt>
+ <references/>
+ <codecompletion>
+ <automaticCodeCompletion>false</automaticCodeCompletion>
+ <automaticArgumentsHint>true</automaticArgumentsHint>
+ <automaticHeaderCompletion>true</automaticHeaderCompletion>
+ <codeCompletionDelay>250</codeCompletionDelay>
+ <argumentsHintDelay>400</argumentsHintDelay>
+ <headerCompletionDelay>250</headerCompletionDelay>
+ <showOnlyAccessibleItems>false</showOnlyAccessibleItems>
+ <completionBoxItemOrder>0</completionBoxItemOrder>
+ <howEvaluationContextMenu>true</howEvaluationContextMenu>
+ <showCommentWithArgumentHint>true</showCommentWithArgumentHint>
+ <statusBarTypeEvaluation>false</statusBarTypeEvaluation>
+ <namespaceAliases>std=_GLIBCXX_STD;__gnu_cxx=std</namespaceAliases>
+ <processPrimaryTypes>true</processPrimaryTypes>
+ <processFunctionArguments>false</processFunctionArguments>
+ <preProcessAllHeaders>false</preProcessAllHeaders>
+ <parseMissingHeadersExperimental>false</parseMissingHeadersExperimental>
+ <resolveIncludePathsUsingMakeExperimental>false</resolveIncludePathsUsingMakeExperimental>
+ <alwaysParseInBackground>true</alwaysParseInBackground>
+ <usePermanentCaching>true</usePermanentCaching>
+ <alwaysIncludeNamespaces>false</alwaysIncludeNamespaces>
+ <includePaths>.;</includePaths>
+ </codecompletion>
+ </kdevcppsupport>
+</kdevelop>
--- /dev/null
+Summary: library with asynchronous io functionality for Intra2net programs
+Name: libsimpleio
+Version: ##VERSION##
+Release: 1
+License: Intranator License
+Group: Intranator
+Vendor: Intra2net AG
+Source: %{name}-%{version}.tar.gz
+Buildroot: /tmp/%{name}-%{version}-root
+Prefix: /usr/intranator
+Requires: libi2ncommon >= 1.0
+Requires: boost >= 1.32.0
+BuildPrereq: libtool
+BuildRequires: boost-devel >= 1.32.0
+
+
+%description
+library with asynchronous io functionality for Intra2net programs.
+
+
+%package devel
+Summary: library with asynchronous io functionality for Intra2net programs
+Group: Intranator/Development
+Requires: libi2ncommon-devel
+Requires: boost-devel >= 1.32.0
+
+
+%description devel
+library with asynchronous io functionality for Intra2net programs
+
+%prep
+%setup -q
+
+%build
+export PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/intranator/lib/pkgconfig
+autoreconf --force -i
+./configure $RPM_BUILD_WITH_OPTIMIZE --prefix=%{prefix}
+dmake
+
+dmake check
+
+%install
+make DESTDIR=$RPM_BUILD_ROOT install
+
+%clean
+rm -fr $RPM_BUILD_ROOT
+
+%post
+/sbin/ldconfig
+
+%files
+%defattr(-,root,root)
+%doc LICENSE
+%{prefix}/lib/libsimpleio.so*
+
+%files devel
+%defattr(-,root,root)
+%{prefix}/lib/*.*a*
+%{prefix}/lib/pkgconfig/*.pc
+%{prefix}/include/
--- /dev/null
+INCLUDES = @LIBI2NCOMMON_CFLAGS@ @BOOST_CPPFLAGS@
+METASOURCES = AUTO
+lib_LTLIBRARIES = libsimpleio.la
+libsimpleio_la_SOURCES = simplecallout.cpp simpleio.cpp simplepipe.cpp \
+ simpleprocess.cpp simplesocket.cpp simpletimer.cpp
+include_HEADERS = simplecallout.hpp simpleio.hpp simplepipe.hpp \
+ simpleprocess.hpp simplesocket.hpp simpletimer.hpp
+libsimpleio_la_LIBADD = @LIBI2NCOMMON_LIBS@ @BOOST_LDFLAGS@ @BOOST_SIGNALS_LIB@
+
+libsimpleio_la_LDFLAGS = -version-info @LIBSIMPLEIO_LIB_VERSION@
+
+pkgconfigdir=$(libdir)/pkgconfig
+pkgconfig_DATA= libsimpleio.pc
--- /dev/null
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libsimpleio
+Description: asynchrounous io lib
+Version: @VERSION@
+Requires: libi2ncommon
+Libs: -L${libdir} -lsimpleio
+Cflags: -I${includedir}
--- /dev/null
+/** @file
+ *
+ * @copyright © Copyright 2008 by Intra2net AG
+ * @license commercial
+ *
+ * info@intra2net.com
+ */
+
+#include "simplecallout.hpp"
+
+#include <tracefunc.hpp>
+#include <logfunc.hpp>
+
+#include <map>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+namespace
+{
+
+typedef boost::shared_ptr< Detail::Caller > CallerPtr;
+
+typedef std::map< unsigned long, CallerPtr > CallMap;
+
+
+unsigned long l_last_id=0;
+
+CallMap l_call_map;
+
+
+/**
+ * @brief creates a new id value for a call out.
+ * @return the new id.
+ *
+ * The id value is basically just a counter.
+ * It is implemented in a way that it can not be 0 and can deal with wrap arounds.
+ */
+unsigned long create_call_out_id_value()
+{
+ while ( l_call_map.find(++l_last_id) != l_call_map.end() and l_last_id != 0);
+ return l_last_id;
+} // eo create_call_out_id_value
+
+
+void add_call( CallerPtr caller )
+{
+ if (caller->joinId())
+ {
+ l_call_map[ caller->getCallOutId().getValue() ] = caller;
+ }
+} // eo add_call
+
+
+bool remove_call( unsigned long id_value )
+{
+ CallMap::iterator it= l_call_map.find(id_value);
+ if (it != l_call_map.end())
+ {
+ l_call_map.erase(it);
+ return true;
+ }
+ return false;
+} // eo remove_call(unsigned long)
+
+
+CallerPtr get_call(unsigned long id_value)
+{
+ CallMap::iterator it= l_call_map.find(id_value);
+ if (it != l_call_map.end())
+ {
+ return it->second;
+ }
+ return CallerPtr();
+} // eo get_call(unsigned long)
+
+
+bool has_call( unsigned long id_value )
+{
+ CallMap::iterator it= l_call_map.find(id_value);
+ return (it != l_call_map.end() );
+} // eo has_call(unsigned long)
+
+
+
+} // eo namespace <anonymous>
+
+
+
+/*
+** implementation of class CallOutId
+*/
+
+CallOutId::CallOutId()
+: m_value(0)
+{
+} // eo CallOutId::CallOutId()
+
+
+CallOutId::CallOutId(unsigned long value)
+: m_value(value)
+{
+} // eo CallOutId::CallOutId(unsigned long)
+
+
+bool CallOutId::thaw() const
+{
+ if (m_caller_weak_ptr.expired())
+ {
+ return false;
+ }
+ CallerPtr call_ptr= get_call(m_value);
+ if (call_ptr)
+ {
+ return call_ptr->thaw();
+ }
+ return false;
+} // eo CallOutId::thaw() const
+
+
+bool CallOutId::remove()
+{
+ if (m_caller_weak_ptr.expired())
+ {
+ return false;
+ }
+ unsigned long value= m_value;
+ m_value= 0;
+ return remove_call(value);
+} // eo CallOutId::remove()
+
+
+bool CallOutId::active() const
+{
+ return m_value!=0 and not m_caller_weak_ptr.expired() and has_call(m_value);
+} // eo CallOutId::active() const
+
+
+
+namespace Detail
+{
+
+/*
+** implementation of class Caller
+*/
+
+Caller::Caller( boost::function< void() > f, long delta_sec, long delta_msec, bool frozen)
+: TimerBase()
+, m_call_out_id( create_call_out_id_value() )
+, m_func(f)
+, m_waiting(frozen)
+{
+ SCOPETRACKER();
+ setDeltaWhenTime( delta_sec, delta_msec);
+} // eo Caller::Caller(boost::function< void() >,long)
+
+
+Caller::~Caller()
+{
+ SCOPETRACKER();
+} // eo Caller::~Caller()
+
+
+void Caller::execute()
+{
+ SCOPETRACKER();
+ // NOTE: since the func may throw an exception, we first get a shared pointer
+ // (to prevent early deletion) and then we remove us from the call map.
+ CallerPtr ptr= shared_from_this();
+ m_call_out_id.remove();
+
+ if (m_func and not m_waiting)
+ {
+ m_func(); // may throw..., but at this point it doesn't harm.
+ //( it may harm at other places,...)
+ }
+} // eo Caller::execute()
+
+
+bool Caller::thaw()
+{
+ if (m_waiting)
+ {
+ m_waiting= false;
+ setDeltaWhenTime( 0, 0 );
+ return true;
+ }
+ return false;
+} // eo Caller::thaw()
+
+
+bool Caller::joinId()
+{
+ if (m_call_out_id.m_caller_weak_ptr.expired())
+ {
+ m_call_out_id.m_caller_weak_ptr= shared_from_this();
+ activate();
+ return true;
+ }
+ return false;
+} // eo Caller::joinId()
+
+
+} // eo namespace Detail
+
+
+
+/**
+ * @brief remove a pending call by id.
+ *
+ * @param id the call id which should be removed.
+ * @return @a true iff the call was removed, @a false if no call with the given id was found.
+ */
+bool removeCallOut( CallOutId& id )
+{
+ return id.remove();
+} // eo removeCallOut(CallOutId&)
+
+
+
+template<>
+CallOutId callOut( boost::function< void() > f, long delta_sec)
+{
+ CallerPtr caller( new Detail::Caller(f,delta_sec) );
+ add_call(caller);
+ return caller->getCallOutId();
+} // eo callOut(boost::function< void() >,long)
+
+template<>
+CallOutId callOut( boost::function< void() > f, int delta_sec)
+{
+ return callOut<long>(f,delta_sec);
+} // eo callOut(boost::function< void() >,int)
+
+
+template<>
+CallOutId callOut( boost::function< void() > f, double delta_sec )
+{
+ long delta_sec_i = (long)delta_sec;
+ long delta_sec_m = (long)((delta_sec - (double)delta_sec_i)*1000.0);
+ CallerPtr caller( new Detail::Caller(f,delta_sec_i, delta_sec_m) );
+ add_call(caller);
+ return caller->getCallOutId();
+} // eo callOut(boost::function< void() >,double)
+
+
+template<>
+CallOutId callOut( boost::function< void() > f, float delta_sec )
+{
+ return callOut<double>(f,delta_sec);
+} // eo callOut(boost::function< void() >,float)
+
+
+
+
+template<>
+CallOutId frozenCall( boost::function< void() > f, long delta_sec)
+{
+ CallerPtr caller( new Detail::Caller(f,delta_sec, 0, true) );
+ add_call(caller);
+ return caller->getCallOutId();
+} // eo frozenCall(boost::function< void() >,long)
+
+template<>
+CallOutId frozenCall( boost::function< void() > f, int delta_sec)
+{
+ return frozenCall<long>(f,delta_sec);
+} // eo frozenCall(boost::function< void() >,int)
+
+
+template<>
+CallOutId frozenCall( boost::function< void() > f, double delta_sec )
+{
+ long delta_sec_i = (long)delta_sec;
+ long delta_sec_m = (long)((delta_sec - (double)delta_sec_i)*1000.0);
+ CallerPtr caller( new Detail::Caller(f,delta_sec_i, delta_sec_m, true) );
+ add_call(caller);
+ return caller->getCallOutId();
+} // eo frozenCall(boost::function< void() >,double)
+
+
+template<>
+CallOutId frozenCall( boost::function< void() > f, float delta_sec )
+{
+ return frozenCall<double>(f,delta_sec);
+} // eo frozenCall(boost::function< void() >,float)
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
--- /dev/null
+/** @file
+ * @brief provides a method for delayed execution of functions.
+ *
+ *
+ * @copyright © Copyright 2008 by Intra2net AG
+ * @license commercial
+ *
+ * info@intra2net.com
+ */
+
+#ifndef __SIMPLEIO_SIMPLECALLOUT_HPP_
+#define __SIMPLEIO_SIMPLECALLOUT_HPP_
+
+#include "simpleio.hpp"
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+// forward declarations:
+namespace Detail {
+
+class Caller;
+
+typedef boost::shared_ptr< Caller > CallerPtr;
+typedef boost::weak_ptr< Caller > CallerWeakPtr;
+
+} // eo namespace Detail
+
+
+/**
+ * @brief represents an id for a deferred call.
+ *
+ * Also provides methods for modifying the call
+ * (like thaw or delete it).
+ */
+class CallOutId
+{
+ friend class Detail::Caller;
+
+ public:
+ CallOutId();
+
+ unsigned long getValue() const {return m_value;}
+
+ bool thaw() const;
+ bool remove();
+
+ bool active() const;
+
+ private:
+
+ CallOutId(unsigned long value);
+
+ private:
+
+ unsigned long m_value;
+
+ Detail::CallerWeakPtr m_caller_weak_ptr;
+
+}; // eo class CallOutId
+
+
+
+/*
+**
+*/
+
+namespace Detail {
+
+/**
+ * @brief tool class for holding and executing a deferred call.
+ *
+ */
+class Caller
+: public TimerBase
+, public boost::enable_shared_from_this< Caller >
+{
+ public:
+ Caller( boost::function< void() > f, long delta_sec, long delta_msec=0, bool frozen=false );
+ virtual ~Caller();
+
+ CallOutId getCallOutId() const { return m_call_out_id; }
+
+ bool thaw();
+
+ bool joinId();
+
+ protected:
+
+ virtual void execute();
+
+ private:
+
+ CallOutId m_call_out_id;
+ boost::function< void() > m_func;
+ bool m_waiting;
+}; // eo class Caller
+
+
+} // eo namespace Detail
+
+/*
+**
+*/
+
+/**
+ * @brief initiates a deferred call of a function.
+ *
+ * @param f the function which should be called.
+ * @param delta_sec the delta time (in seconds) when the function should be called.
+ * @return an id to identify the call (may be used for preliminary removal of the call)
+ */
+template< typename F >
+CallOutId callOut( boost::function< void() > f, F delta_sec );
+
+template<> CallOutId callOut( boost::function< void() > f, long delta_sec );
+template<> CallOutId callOut( boost::function< void() > f, double delta_sec );
+template<> CallOutId callOut( boost::function< void() > f, float delta_sec );
+template<> CallOutId callOut( boost::function< void() > f, int delta_sec );
+
+
+/**
+ * @brief initiates a frozen call of a function.
+ *
+ * @param f the function which should be called.
+ * @param delta_sec the delta time (in seconds) when the call will be (silently) removed.
+ * @return an id to identify the call; neccessary for thaw the call.
+ */
+template< typename F >
+CallOutId frozenCall( boost::function< void() > f, F delta_sec);
+
+template<> CallOutId frozenCall( boost::function< void() > f, long delta_sec );
+template<> CallOutId frozenCall( boost::function< void() > f, double delta_sec );
+template<> CallOutId frozenCall( boost::function< void() > f, float delta_sec );
+template<> CallOutId frozenCall( boost::function< void() > f, int delta_sec );
+
+
+
+bool removeCallOut( CallOutId& id );
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
+
+#endif
--- /dev/null
+/** @file
+ *
+ *
+ * @copyright Copyright © 2007-2008 by Intra2net AG
+ * @license commercial
+ * @contact info@intra2net.com
+ */
+
+//#define NOISEDEBUG
+
+#include "simpleio.hpp"
+
+#include <list>
+#include <vector>
+#include <map>
+#include <algorithm>
+#include <utility>
+
+#include <sys/poll.h>
+#include <sys/time.h>
+#include <sys/socket.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <boost/bind.hpp>
+
+#include <signalfunc.hpp>
+#include <timefunc.hxx>
+
+#include <iostream>
+
+#ifdef NOISEDEBUG
+#include <iostream>
+#include <iomanip>
+#define DOUT(msg) std::cout << msg << std::endl
+#define FODOUT(obj,msg) std::cout << typeid(*obj).name() << "[" << obj << "]:" << msg << std::endl
+//#define ODOUT(msg) std::cout << typeid(*this).name() << "[" << this << "]:" << msg << std::endl
+#define ODOUT(msg) std::cout << __PRETTY_FUNCTION__ << "[" << this << "]:" << msg << std::endl
+#else
+#define DOUT(msg) do {} while (0)
+#define FODOUT(obj,msg) do {} while (0)
+#define ODOUT(msg) do {} while (0)
+#endif
+
+namespace
+{
+
+
+/*
+ * configuration:
+ */
+
+
+const int c_max_poll_wait= 10*60*1000; // maximal poll wait (while in backend loop): 10 min
+
+
+/**
+ * contains internal helper structs and functions for io handling.
+ */
+namespace internal_io
+{
+
+/**
+ * extends struct pollfd with some convenience functions
+ */
+struct PollFd : public ::pollfd
+{
+ PollFd()
+ {
+ fd= 0;
+ events= revents= 0;
+ } // eo PollFd
+
+
+ /**
+ * initializes the struct with a given file descriptor and clears the event mask(s).
+ * @param _fd
+ */
+ PollFd(int _fd)
+ {
+ fd= _fd;
+ events= revents= 0;
+ } // eo PollFd
+
+
+ /**
+ * set that we want to be notified about new incoming data
+ */
+ void setPOLLIN() { events |= POLLIN; }
+
+ /**
+ * set that we want to be notified if we can send (more) data.
+ */
+ void setPOLLOUT() { events |= POLLOUT; }
+
+}; // eo struct PollFd
+
+
+typedef std::vector<PollFd> PollVector;
+typedef std::map<int,PollVector::size_type> FdPollMap;
+typedef std::map<int,I2n::SimpleIo::IOImplementation*> FdIOMap;
+
+
+/**
+ * struct for interfacing our local structures with poll()
+ */
+struct PollDataCluster
+{
+ PollVector m_poll_vector;
+ FdPollMap m_fd_poll_map;
+ FdIOMap m_read_fd_io_map;
+ FdIOMap m_write_fd_io_map;
+
+ void add_read_fd( int fd, I2n::SimpleIo::IOImplementation* io);
+ void add_write_fd( int fd, I2n::SimpleIo::IOImplementation* io);
+
+ pollfd* get_pollfd_ptr();
+ unsigned int get_num_pollfds() const;
+
+}; // eo struct PollDataCluster
+
+
+template<class T>
+class PtrList : public std::list<T*>
+{
+ typedef std::list<T*> inherited;
+ public:
+ bool dirty;
+
+ static int Instances;
+
+ public:
+
+ PtrList()
+ : dirty(false)
+ {
+ ++Instances;
+ } // eo PtrList
+
+
+ ~PtrList()
+ {
+ ODOUT("");
+ --Instances;
+ }
+
+
+ /**
+ * add a new item pointer to the list.
+ *
+ * @param item item pointer which should be added
+ */
+ void add_item(T* item)
+ {
+ typename inherited::iterator it= std::find(inherited::begin(), inherited::end(), item);
+ if (it != inherited::end())
+ {
+ // nothing to do since item is already in the list
+ return;
+ }
+ push_back(item);
+ } // eo add
+
+
+ /**
+ * remove an item pointer from the list by setting NULL at the current position of the item and mark
+ * the list as dirty.
+ *
+ * @param item the io object which should be removed from the list.
+ */
+ void remove_item(T* item)
+ {
+ typename inherited::iterator it= std::find(inherited::begin(), inherited::end(), item);
+ if (it == inherited::end())
+ {
+ // nothing to do:
+ return;
+ }
+ *it = NULL; // forget the pointer
+ dirty= true; // ..and mark the list as dirty (i.e. has NULL elements)
+ } // eo remove
+
+
+ /**
+ * cleans the list of objects by removing the NULL elements (if any).
+ *
+ * @note this function should only be called when it is ensured that no other functions using iterators of this list.
+ */
+ void clean_list()
+ {
+ if (!dirty)
+ {
+ // nothing to do
+ return;
+ }
+ // remove the NULL elements now:
+ erase(
+ std::remove( inherited::begin(), inherited::end(), (T*)NULL),
+ inherited::end() );
+ dirty= false;
+ } // eo clean_list
+
+
+}; // eo class PtrList
+
+
+typedef PtrList<I2n::SimpleIo::IOImplementation> IOList;
+typedef PtrList<I2n::SimpleIo::TimerBase> TimerList;
+
+template<> int IOList::Instances= 0;
+template<> int TimerList::Instances= 0;
+
+
+/**
+ * the (internal) global list of io objects (object pointers)
+ */
+IOList& g_io_list()
+{
+ static IOList _the_io_list;
+ return _the_io_list;
+};
+
+
+/**
+ * the (internal) global list of timer objects (object pointers)
+ */
+TimerList& g_timer_list()
+{
+ static TimerList _the_timer_list;
+ return _the_timer_list;
+}
+
+/*
+ * implementation of PollDataCluster
+ */
+
+
+/**
+ * add a new file descriptor to the read list.
+ *
+ * @param fd the file descriptor.
+ * @param io the io object which uses the fd for reading.
+ */
+void PollDataCluster::add_read_fd( int fd, I2n::SimpleIo::IOImplementation* io)
+{
+ FdPollMap::iterator itPollMap = m_fd_poll_map.find(fd);
+ if (itPollMap != m_fd_poll_map.end())
+ {
+ m_poll_vector[ itPollMap->second ].setPOLLIN();
+ }
+ else
+ {
+ PollFd item(fd);
+ item.setPOLLIN();
+ m_fd_poll_map[fd] = m_poll_vector.size();
+ m_poll_vector.push_back( item );
+ }
+ m_read_fd_io_map[fd]= io;
+} // eo PollDataCluster::add_read_fd
+
+
+/**
+ * add a new file descriptor to the write list.
+ *
+ * @param fd the file descriptor.
+ * @param io the io object which uses the fd for writing.
+ */
+void PollDataCluster::add_write_fd( int fd, I2n::SimpleIo::IOImplementation* io)
+{
+ FdPollMap::iterator itPollMap = m_fd_poll_map.find(fd);
+ if (itPollMap != m_fd_poll_map.end())
+ {
+ m_poll_vector[ itPollMap->second ].setPOLLOUT();
+ }
+ else
+ {
+ PollFd item(fd);
+ item.setPOLLOUT();
+ m_fd_poll_map[fd] = m_poll_vector.size();
+ m_poll_vector.push_back( item );
+ }
+ m_write_fd_io_map[fd]= io;
+} // eo PollDataCluster::add_write_fd
+
+
+/**
+ * returns a pointer to a pollfd array; suitable for passing to poll()
+ *
+ * @return pointer to pollfd array
+ */
+pollfd* PollDataCluster::get_pollfd_ptr()
+{
+ return m_poll_vector.empty() ? NULL : &m_poll_vector.front();
+} // eo get_pollfd_ptr
+
+
+/**
+ * returns the number of entries in the pollfd array; suitable for passing to poll()
+ *
+ * @return the number of entries in the pollfd array
+ */
+unsigned int PollDataCluster::get_num_pollfds() const
+{
+ return m_poll_vector.size();
+} // eo get_num_pollfds
+
+
+
+} // eo namespace internal_io
+
+
+/*
+ * some internal tool functions and structures
+ */
+
+struct FilterMatch {
+ I2n::SimpleIo::FilterBasePtr m_filter;
+
+ FilterMatch(I2n::SimpleIo::FilterBasePtr filter)
+ : m_filter(filter)
+ {}
+
+ bool operator () (const I2n::SimpleIo::FilterBasePtr& item)
+ {
+ return item && item == m_filter;
+ }
+
+}; // eo struct FilterMatch
+
+
+void get_current_real_time(long& current_sec, long& current_msec)
+{
+ struct timeval tv;
+ gettimeofday(&tv,NULL);
+ current_sec= tv.tv_sec;
+ current_msec= (tv.tv_usec / 1000);
+ if (current_msec >= 1000)
+ {
+ current_sec += (current_msec / 1000);
+ current_msec%= 1000;
+ }
+} // eo get_current_real_time
+
+
+void get_current_monotonic_time(long& current_sec, long& current_msec)
+{
+ long nsec;
+ if (monotonic_clock_gettime(current_sec,nsec))
+ {
+ current_msec= nsec / 1000000L;
+ }
+ else
+ {
+ //fallback...
+ get_current_real_time(current_sec,current_msec);
+ }
+} // eo get_current_monotonic_time
+
+
+
+} // eo anonymous namespace
+
+
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+/**
+ * @brief gets the current time as MilliTime structure.
+ * @param[out] mt reference to the MilliTime strcucture which is filled with the result.
+ */
+void get_current_real_time(MilliTime& mt)
+{
+ long sec, msec;
+ ::get_current_real_time(sec,msec);
+ mt.set(sec,msec);
+} // eo get_current_real_time
+
+
+/**
+ * @brief gets the current time as MilliTime structure.
+ * @param[out] mt reference to the MilliTime strcucture which is filled with the result.
+ */
+void get_current_monotonic_time(MilliTime& mt)
+{
+ long sec, msec;
+ ::get_current_monotonic_time(sec,msec);
+ mt.set(sec,msec);
+} // eo get_current_monotonic_time
+
+
+/*
+ * implementation of MilliTime
+ */
+
+MilliTime::MilliTime(long sec, long msec)
+: mt_sec(sec), mt_msec(msec)
+{
+ normalize();
+} // eo MilliTime::MilliTime
+
+
+void MilliTime::set(long sec, long msec)
+{
+ mt_sec= sec;
+ mt_msec= msec;
+ normalize();
+} // eo MilliTime::set
+
+
+/**
+ * normalizes the values, so that mt_msec has a value between 0 and 999.
+ */
+void MilliTime::normalize()
+{
+ if (mt_msec < 0)
+ {
+ mt_sec += (mt_msec / 1000) - 1;
+ mt_msec = (mt_msec % 1000) + 1000;
+ }
+ else if (mt_msec>=1000)
+ {
+ mt_sec+= (mt_msec / 1000);
+ mt_msec %= 1000;
+ }
+} // eo MilliTime::normalize
+
+
+/**
+ * determine if the represented point in time is before another one.
+ * @param other the other point in time.
+ * @return true if the own point in time is before the other one.
+ */
+bool MilliTime::operator < (MilliTime& other)
+{
+ normalize();
+ other.normalize();
+ return
+ (mt_sec < other.mt_sec)
+ || (( mt_sec == other.mt_sec) && (mt_msec < other.mt_msec));
+} // eo MilliTime::operator <
+
+
+/**
+ * determine if two point in times are equal.
+ * @param other the point in time to compare with.
+ * @return true if the represented times are equal.
+ */
+bool MilliTime::operator == (MilliTime& other)
+{
+ normalize();
+ other.normalize();
+ return (( mt_sec == other.mt_sec) && (mt_msec == other.mt_msec));
+} // eo MilliTime::operator <
+
+/**
+ * @brief subtracts a time delta from the object.
+ * @param lhs the time delta to subtract.
+ * @return reference to the object itself.
+ */
+MilliTime& MilliTime::operator -= (const MilliTime& lhs)
+{
+ mt_sec -= lhs.mt_sec;
+ mt_msec -= lhs.mt_msec;
+} // eo operator -=
+
+
+/**
+ * @brief adds a time delta from the object.
+ * @param lhs the time delta to add.
+ * @return reference to the object itself.
+ */
+MilliTime& MilliTime::operator += (const MilliTime& lhs)
+{
+ mt_sec += lhs.mt_sec;
+ mt_msec += lhs.mt_msec;
+} // eo operator +=
+
+
+/*
+ * implementation of TimerBase
+ */
+
+/**
+ * constructor. Adds the object to the internal timer list.
+ */
+TimerBase::TimerBase()
+: m_active(false)
+, m_marked(false)
+{
+ internal_io::g_timer_list().add_item(this);
+} // eo TimerBase::TimerBase
+
+
+/**
+ * destructor. Removes the object from the internal timer list.
+ */
+TimerBase::~TimerBase()
+{
+ ODOUT("enter");
+ if (internal_io::TimerList::Instances)
+ {
+ ODOUT("remove from list");
+ internal_io::g_timer_list().remove_item(this);
+ }
+} // eo TimerBase::~TimerBase
+
+
+/**
+ * @brief returns the point in time when the time is executed in real time.
+ * @return the point in time when the timer is to be executed.
+ */
+MilliTime TimerBase::getRealWhenTime() const
+{
+ MilliTime mono_time;
+ MilliTime real_time;
+ get_current_monotonic_time(mono_time);
+ get_current_real_time(real_time);
+ MilliTime result= m_when - mono_time + real_time;
+ return result;
+} // eo TimerBase::getRealWhenTime() const
+
+
+/**
+ * sets the time when the event should be executed.
+ * @param sec the seconds part of the point in time.
+ * @param msec the milliseconds part of the point in time.
+ */
+void TimerBase::setWhenTime(long sec, long msec)
+{
+ m_when.set(sec,msec);
+ m_marked= false;
+} // eo TimerBase::setWhenTime
+
+
+/**
+ * sets the time when the event should be executed.
+ * @param mt the point in time.
+ */
+void TimerBase::setWhenTime(const MilliTime& mt)
+{
+ m_when= mt;
+ m_marked= false;
+} // eo TimerBase::setWhenTime
+
+
+/**
+ * sets the time delta measured from current time when the event should be executed.
+ * @param sec the seconds of the time delta
+ * @param msec the milli seconds of the time delta
+ */
+void TimerBase::setDeltaWhenTime(long sec, long msec)
+{
+ setDeltaWhenTime( MilliTime(sec,msec) );
+} // eo TimerBase::setWhenTime
+
+
+
+/**
+ * sets the time delta measured from current time when the event should be executed.
+ * @param mt the time delta
+ */
+void TimerBase::setDeltaWhenTime(const MilliTime& mt)
+{
+ get_current_monotonic_time(m_when);
+ m_when+= mt;
+ m_marked= false;
+} // eo TimerBase::setWhenTime
+
+
+/**
+ * set the active state of the timer event.
+ * @param active determines if the object should be active (default: yes).
+ */
+void TimerBase::activate(bool active)
+{
+ m_active = active;
+ if (!active)
+ {
+ // clear the mark if we are not active.
+ m_marked= false;
+ }
+} // eo TimerBase::activate
+
+
+/** @fn void TimerBase::deactivate()
+ * deactivates the event by clearing the active state.
+ */
+
+
+/**
+ * called when the timer event occured.
+ */
+void TimerBase::execute()
+{
+} // eo TimerBase::execute
+
+
+/*
+ * implementation of FilterBase class
+ */
+
+
+FilterBase::FilterBase()
+: m_io(NULL)
+{
+} // eo FilterBase::FilterBase()
+
+
+/**
+ * injects incoming data.
+ * @param data the new data
+ */
+void FilterBase::injectIncomingData(const std::string& data)
+{
+ if (m_io)
+ {
+ FilterBasePtr ptr= get_ptr_as< FilterBase >();
+ if (ptr)
+ {
+ m_io->injectIncomingData(ptr,data);
+ }
+ }
+} // FilterBase::injectIncomingData(const std::string&)
+
+
+/**
+ * injects outgoing data.
+ * @param data the new data
+ */
+void FilterBase::injectOutgoingData(const std::string& data)
+{
+ if (m_io)
+ {
+ FilterBasePtr ptr= get_ptr_as< FilterBase >();
+ if (ptr)
+ {
+ m_io->injectOutgoingData(ptr,data);
+ }
+ }
+} // eo FilterBase::injectOutgoingData(const std::string&)
+
+
+
+/**
+ * called when EOF detected on incoming channel (or incoming channel closed)
+ */
+void FilterBase::endOfIncomingData()
+{
+} // eo FilterBase::endOfIncomingData()
+
+/**
+ * called when the filter should reset.
+ * This is used when a new channel is opened or when the filter is taken out of a filter chain.
+ */
+void FilterBase::reset()
+{
+} // eo FilterBase::reset()
+
+
+/*
+ * implementation of IOImplementation class
+ */
+
+
+/**
+ * constructor for the base io class.
+ *
+ * Also adds the object to internal list of io objects (which is used by the backend).
+ *
+ * @param read_fd the file descriptor which should be used for reading (default -1 for no value)
+ * @param write_fd the file descriptor which should be used for writing (default -1 for no value)
+ */
+IOImplementation::IOImplementation(int read_fd, int write_fd)
+: m_read_fd(read_fd)
+, m_write_fd(write_fd)
+, m_eof(false)
+, m_not_writable(false)
+, m_input_buffer()
+, m_output_buffer()
+, m_marked_for_reading(false)
+, m_marked_for_writing(false)
+{
+ internal_io::g_io_list().add_item(this);
+} // eo IOImplementation::IOImplementation
+
+
+/**
+ * destructor of the base io class.
+ *
+ * Removes the object from the interal list of io objects.
+ */
+IOImplementation::~IOImplementation()
+{
+ close();
+ if (internal_io::IOList::Instances)
+ {
+ internal_io::g_io_list().remove_item(this);
+ }
+ // now clear the filters:
+ while (! m_filter_chain.empty() )
+ {
+ FilterChain::iterator it = m_filter_chain.begin();
+ (*it)->reset();
+ (*it)->m_io= NULL;
+ //TODO: signal the filter that it is removed ?!
+ m_filter_chain.erase(it);
+ }
+} // eo IOImplementation::~IOImplementation
+
+
+/**
+ * adds another filter to the filter chain.
+ * @param filter pointer to the new filter.
+ */
+void IOImplementation::addFilter
+(
+ FilterBasePtr filter
+)
+{
+ if (!filter)
+ {
+ return; // nothing to do
+ }
+ if (filter->m_io)
+ {
+ filter->m_io->removeFilter(filter);
+ }
+ m_filter_chain.push_back( filter );
+} // eo IOImplementation::addFilter
+
+
+/**
+ * removes a filter from the filter chain.
+ * @param filter the pointer to the filter which is removed.
+ * @note if the filter is removed the class gives away the ownership; i.,e. the caller is responsible for
+ * deleting the filter if it was dynamically allocated.
+ */
+void IOImplementation::removeFilter
+(
+ FilterBasePtr filter
+)
+{
+ FilterChain::iterator it =
+ std::find_if( m_filter_chain.begin(), m_filter_chain.end(), FilterMatch(filter) );
+ if (it != m_filter_chain.end())
+ {
+ filter->reset();
+ filter->m_io= NULL;
+ //TODO: signal the filter that it is removed ?!
+ m_filter_chain.erase(it);
+ }
+} // eo IOImplementation::removeFilter
+
+
+/**
+ * closes the file descriptors (/ the connection).
+ *
+ * @param direction the direction which should be closed (default: @a Direction::both for all).
+ */
+void IOImplementation::close(Direction direction)
+{
+ bool had_read_fd= (m_read_fd >= 0);
+ m_errno= 0;
+ if (direction == Direction::unspecified) direction= Direction::both;
+ if (direction != Direction::both && m_read_fd==m_write_fd && m_read_fd>=0)
+ { // special case: half closed (socket) connections...
+ // NOTE: for file descriptors m_errno will set to ENOTSOCK, but since we "forget" the desired part
+ // (read_fd or write_fd) this class works as desired.
+ switch(direction)
+ {
+ case Direction::in:
+ {
+ int res= ::shutdown(m_read_fd, SHUT_RD);
+ if (res<0)
+ {
+ m_errno= errno;
+ }
+ m_read_fd= -1;
+ if (!m_eof)
+ {
+ for(FilterChain::iterator it= m_filter_chain.begin();
+ it != m_filter_chain.end();
+ ++it)
+ {
+ (*it)->endOfIncomingData();
+ }
+ }
+ }
+ return;
+
+ case Direction::out:
+ {
+ int res= ::shutdown(m_write_fd, SHUT_WR);
+ if (res<0)
+ {
+ m_errno= errno;
+ }
+ m_write_fd= -1;
+ m_output_buffer.clear();
+ }
+ return;
+ }
+ }
+ if (m_write_fd >= 0 && (direction & Direction::out) )
+ {
+ int res1 = ::close(m_write_fd);
+ if (m_write_fd == m_read_fd)
+ {
+ m_read_fd= -1;
+ }
+ m_write_fd= -1;
+ m_output_buffer.clear();
+ if (res1<0) m_errno= errno;
+ }
+ if (m_read_fd >=0 && (direction & Direction::in) )
+ {
+ int res1 = ::close(m_read_fd);
+ m_read_fd= -1;
+ if (res1<0) m_errno= errno;
+ }
+ if (had_read_fd && !m_eof && (m_read_fd<0))
+ {
+ for(FilterChain::iterator it= m_filter_chain.begin();
+ it != m_filter_chain.end();
+ ++it)
+ {
+ (*it)->endOfIncomingData();
+ }
+ }
+} // eo IOImplementation::close
+
+
+/**
+ * determines if the io class wants to read data.
+ * Default implementation checks only for a valid file descriptor value.
+ *
+ * @return @a true if the objects wants to read data
+ */
+bool IOImplementation::wantRead()
+{
+ return (m_read_fd >= 0) && ! m_eof;
+} // eo IOImplementation::wantRead
+
+
+/**
+ * determines if the io class wants to write data.
+ * Default implementation checks for a valid file descriptor value and if the object
+ * cannot write data immediately.
+ *
+ * @return @a true if the objects wants to write data
+ */
+bool IOImplementation::wantWrite()
+{
+ return (m_write_fd >= 0) && ! m_marked_for_writing && ! m_not_writable;
+} // eo IOImplementation::wantWrite
+
+
+/**
+ * delivers if opened.
+ * The default returns @a true if at least one file descriptor (read or write) is valid.
+ * @return @a true if opened.
+ */
+bool IOImplementation::opened() const
+{
+ return (m_read_fd>=0) || (m_write_fd>=0);
+} // eo IOImplementation::opened() const
+
+
+/**
+ * returns if the read side detected an end of file (EOF).
+ * @return @a true if end of file was detected on read file descriptor (or read file descriptor isn't valid).
+ */
+bool IOImplementation::eof() const
+{
+ return (m_read_fd < 0) || m_eof;
+} // eo IOImplementatio::eof() const
+
+
+/**
+ * @brief returns of the write side didn't detect that it cannot write.
+ * @return @a true if we can write.
+ */
+bool IOImplementation::writable() const
+{
+ return (m_write_fd >=0 ) and not m_not_writable;
+} // eo IOImplementation::writable() const
+
+
+/**
+ * returns if the output buffer is empty.
+ * @return
+ */
+bool IOImplementation::empty() const
+{
+ return m_output_buffer.empty();
+} // eo IOImplementation::empty
+
+/**
+ * puts data into the output buffer and sends it immediately if possible,
+ *
+ * The data is passed through the filter chain before it's stored in the output buffer
+ * (i.e. the output buffer contains data as it should be send directly to the descriptor).
+ * @param _data the data which should be send.
+ */
+void IOImplementation::lowSend(const std::string& _data)
+{
+ std::string data(_data);
+
+ for(FilterChain::reverse_iterator it_filter= m_filter_chain.rbegin();
+ it_filter!= m_filter_chain.rend();
+ ++it_filter)
+ {
+ data= (*it_filter)->filterOutgoingData(data);
+ }
+ m_output_buffer+= data;
+
+ // if we can send immediately, do it:
+ if (! m_output_buffer.empty() && m_marked_for_writing)
+ {
+ doWrite();
+ }
+} // eo IOImplementation::lowSend
+
+
+/**
+ * called by the backend when there is data to read for this object.
+ *
+ * Reads the data from the connection (read file descriptor) and passes the data through the filter chain.
+ * The final data is appended to the input buffer and the signal @a m_signal_read() is called.
+ *
+ * If EOF is detected (i,e, no data was received) then the signal @a m_signal_eof() is called.
+ *
+ * @note overload this method only when You know what You are doing!
+ * (overloading is necessary when handling server sockets.)
+ */
+void IOImplementation::doRead()
+{
+ // static read buffer; should be ok as long as we don't use threads
+ static char buffer[8*1024]; // 8 KiB
+
+ m_errno = 0;
+ if (m_read_fd<0 || !m_marked_for_reading)
+ {
+ ODOUT("exit0; read_fd="<<m_read_fd << " mark=" << m_marked_for_reading);
+ return;
+ }
+
+ // reset the mark:
+ m_marked_for_reading = false;
+
+ // now read the data:
+ ssize_t count;
+ count = ::read(m_read_fd, buffer, sizeof(buffer));
+
+ ODOUT("::read -> " << count);
+
+ // interpret what we got:
+ if (count < 0) // error
+ {
+ m_errno = errno;
+ int fd= m_read_fd;
+
+ switch(m_errno)
+ {
+ case EINVAL:
+ case EBADF:
+ case ECONNRESET:
+ case ENETRESET:
+ if (fd == m_read_fd)
+ {
+ close( (m_read_fd == m_write_fd) ? Direction::both : Direction::in );
+ }
+ break;
+ }
+ }
+ else if (count==0) // EOF
+ {
+ // remember the read fd:
+ int fd = m_read_fd;
+ // remember the EOF:
+ m_eof= true;
+ // signal EOF
+ m_signal_eof();
+ // if the fd is still the same: close it.
+ if (fd == m_read_fd)
+ {
+ close( Direction::in );
+ }
+ }
+ else // we have valid data
+ {
+ std::string data(buffer,count);
+ ODOUT(" got \"" << data << "\"");
+ for(FilterChain::iterator it_filter= m_filter_chain.begin();
+ it_filter != m_filter_chain.end();
+ ++it_filter)
+ {
+ data= (*it_filter)->filterIncomingData(data);
+ }
+ m_input_buffer+= data;
+ m_signal_read();
+ }
+} // eo IOImplementation::doRead
+
+
+/**
+ * interface for filter classes to inject data into the filter chain (emulating new incoming data).
+ * @param from_filter the filter which injects the new data.
+ * @param _data the new data.
+ */
+void IOImplementation::injectIncomingData(FilterBasePtr from_filter, const std::string& _data)
+{
+ FilterChain::iterator it_filter =
+ std::find_if( m_filter_chain.begin(), m_filter_chain.end(), FilterMatch(from_filter) );
+ if (it_filter == m_filter_chain.end())
+ {
+ // dont accept data inject from a unknown filter
+ return;
+ }
+ // well: pass the data through the remaining filters:
+ // NOTE: processing is (nearly) the same as in IOImplementation::doRead()
+ std::string data(_data);
+ for(++it_filter;
+ it_filter != m_filter_chain.end();
+ ++it_filter)
+ {
+ data= (*it_filter)->filterIncomingData(data);
+ }
+ m_input_buffer+= data;
+ m_signal_read();
+} // eo IOImplementation::injectIncomingData(FilterBase*,const std::string&)
+
+
+/**
+ * interface for filter classes to inject data into the filter chain (emulating new outgoing data).
+ * @param from_filter the filter which injects the new data.
+ * @param _data the new data.
+ */
+void IOImplementation::injectOutgoingData(FilterBasePtr from_filter, const std::string& _data)
+{
+ FilterChain::reverse_iterator it_filter =
+ std::find_if( m_filter_chain.rbegin(), m_filter_chain.rend(), FilterMatch(from_filter) );
+ if (it_filter == m_filter_chain.rend())
+ {
+ // dont accept data inject from a unknown filter
+ return;
+ }
+ // well: pass the data through the remaining filters:
+ // NOTE: processing is (nearly) the same as in IOImplementation::lowSend()
+ std::string data(_data);
+ for(++it_filter;
+ it_filter!= m_filter_chain.rend();
+ ++it_filter)
+ {
+ data= (*it_filter)->filterOutgoingData(data);
+ }
+ m_output_buffer+= data;
+
+ // if we can send immediately, do it:
+ if (! m_output_buffer.empty() && m_marked_for_writing)
+ {
+ doWrite();
+ }
+} // eo IOImplementation::injectOutgoingData(FilterBase*,const std::string&)
+
+
+/**
+ * set the read file descriptor.
+ * Although a derived class can also set the read fd directly; this method should be used
+ * for this task since it updates some flags on the fd for async operation.
+ * @param fd the new read file descriptor.
+ */
+void IOImplementation::setReadFd(int fd)
+{
+ // test if we already have a valid descriptor (and may have to close it):
+ if (m_read_fd >=0 )
+ {
+ if (m_read_fd == fd)
+ {
+ // fd was already right; consider it to be ok.
+ return;
+ }
+ close(Direction::in);
+ }
+ // reset our errno:
+ m_errno= 0;
+ // if the new descriptor looks valid, set some flags:
+ if (fd >= 0)
+ {
+ long flags= ::fcntl(fd, F_GETFL);
+ if (flags != -1)
+ {
+ // set the flags for non blocking, async operation
+ flags |= O_NONBLOCK|O_ASYNC;
+ ::fcntl(fd,F_SETFL, flags);
+ }
+ else if ( errno == EBADF )
+ {
+ // well, we seemed to be fed with an invalid descriptor...:
+ m_errno = errno;
+ fd= -1;
+ }
+ }
+ if (fd >= 0) // if still valid:
+ {
+ // set the close-on-exec flag
+ ::fcntl(fd,F_SETFD, FD_CLOEXEC);
+ }
+ m_read_fd= fd;
+ m_marked_for_reading= false;
+ m_eof= false;
+} // eo IOImplementation::setReadFd(int)
+
+
+
+/**
+ * set the write file descriptor.
+ * Although a derived class can also set the write fd directly; this method should be used
+ * for this task since it updates some flags on the fd for async operation.
+ * @param fd the new write file descriptor.
+ */
+void IOImplementation::setWriteFd(int fd)
+{
+ if (m_write_fd >=0 )
+ {
+ if (m_write_fd == fd)
+ {
+ // fd was already right; consider it to be ok.
+ return;
+ }
+ close(Direction::out);
+ }
+ // reset our errno:
+ m_errno= 0;
+ // if the new descriptor looks valid, set some flags:
+ if (fd >= 0)
+ {
+ long flags= ::fcntl(fd, F_GETFL);
+ if (flags != -1)
+ {
+ // set the flags for non blocking, async operation
+ flags |= O_NONBLOCK|O_ASYNC;
+ ::fcntl(fd,F_SETFL, flags);
+ }
+ else if (errno == EBADF)
+ {
+ // well, we seemed to be fed with an invalid descriptor...:
+ m_errno = errno;
+ fd= -1;
+ }
+ }
+ if (fd >= 0) // if still valid:
+ {
+ // set the close-on-exec flag
+ ::fcntl(fd,F_SETFD, FD_CLOEXEC);
+ }
+ m_write_fd = fd;
+ m_marked_for_writing= false;
+ m_not_writable= false;
+} // eo IOImplementation::setWriteFd(int)
+
+
+
+/**
+ * called by the backend when this object can write data.
+ *
+ * If some data was sended, the signal @a m_signal_write is called.
+ *
+ * @internal tries to write all buffered data to output; if this succeeds,
+ * the connection is assumed to be still able to accept more data.
+ * (i.e. the internal write mark is kept!)
+ *
+ * @note overload this method only when You know what You are doing!
+*/
+void IOImplementation::doWrite()
+{
+ m_errno = 0;
+ if ( m_write_fd<0 || !m_marked_for_writing || m_output_buffer.empty())
+ {
+ return;
+ }
+
+ ODOUT("doWrite, d=\"" << m_output_buffer << "\"");
+
+ //reset mark:
+ m_marked_for_writing= false;
+
+ // now write the data
+ ssize_t count= ::write( m_write_fd, m_output_buffer.data(), m_output_buffer.size());
+
+ ODOUT("::write -> " << count);
+
+ if (count < 0) // error
+ {
+ m_errno= errno;
+ int fd= m_write_fd;
+
+ switch(m_errno)
+ {
+ case EPIPE:
+ m_not_writable= true;
+ // emit a signal
+ m_signal_not_writable();
+ // fall through
+ case EINVAL:
+ case EBADF:
+ case ECONNRESET:
+ case ENETRESET:
+ if (fd == m_write_fd)
+ {
+ close( (m_write_fd == m_read_fd) ? Direction::both : Direction::out );
+ }
+ break;
+ }
+ }
+ else
+ {
+ m_output_buffer.erase(0, count);
+ if (m_output_buffer.empty())
+ {
+ // special case: if we were able to send all the data, we keep the write mark:
+ m_marked_for_writing= true;
+ }
+ }
+ if (count > 0)
+ {
+ m_signal_write();
+ }
+} // eo IOImplementation::doWrite
+
+
+/*
+ * implementation of SimpleIO
+ */
+
+
+SimpleIO::SimpleIO(int read_fd, int write_fd)
+: inherited(read_fd, write_fd)
+{
+ m_signal_read.connect(boost::bind(&SimpleIO::slotReceived,this));
+} // eo SimpleIO::SimpleIO()
+
+
+SimpleIO::~SimpleIO()
+{
+} // eo SimpleIO::~SimpleIO()
+
+
+/**
+ * sends a string.
+ * @param data the string.
+ */
+void SimpleIO::sendString(const std::string& data)
+{
+ lowSend(data);
+} // eo SimpleIO::sendString(const std::string&)
+
+
+/**
+ * emits the signal signalReceived with the received data.
+ * This slot is connected to IOImplementation::m_signal_read.
+ */
+void SimpleIO::slotReceived()
+{
+ std::string data;
+ data.swap(m_input_buffer);
+ signal_received_string(data);
+} // eo SimpleIO::slotReceived()
+
+
+
+/*
+ * implementation of SimpleIO2
+ */
+
+
+SimpleIO2::SimpleIO2(int read_fd, int write_fd)
+: inherited(read_fd, write_fd)
+{
+ m_signal_read.connect(boost::bind(&SimpleIO2::slotReceived,this));
+} // eo SimpleIO2::SimpleIO2()
+
+
+SimpleIO2::~SimpleIO2()
+{
+} // eo SimpleIO2::~SimpleIO2()
+
+
+/**
+ * sends a string.
+ * @param data the string.
+ */
+void SimpleIO2::sendString(const std::string& data)
+{
+ lowSend(data);
+} // eo SimpleIO2::sendString(const std::string&)
+
+
+/**
+ * emits the signal signalReceived with the received data.
+ * This slot is connected to IOImplementation::m_signal_read.
+ */
+void SimpleIO2::slotReceived()
+{
+ std::string data;
+ data.swap(m_input_buffer);
+ signal_received_string(data);
+} // eo SimpleIO2::slotReceived()
+
+
+
+/*
+ * implementation of class Backend (singleton)
+ */
+
+Backend* Backend::g_backend= NULL;
+
+int Backend::m_count_active_steps=0;
+
+
+Backend::Backend()
+: m_count_active_loops(0)
+, m_count_stop_requests(0)
+{
+ SystemTools::ignore_signal( SystemTools::Signal::PIPE );
+} // eo Backend::Backend
+
+
+Backend::~Backend()
+{
+ SystemTools::restore_signal_handler( SystemTools::Signal::PIPE );
+} // eo Backend::~Backend()
+
+/**
+ * delivers pointer to the current backend, instantiating a new backend if there was no current one.
+ *
+ * This should be the only way to access the backend which should be a singleton.
+ *
+ * @return the pointer to the current backend.
+ */
+Backend* Backend::getBackend()
+{
+ if (!g_backend)
+ {
+ g_backend = new Backend();
+ }
+ return g_backend;
+} // eo Backend::getBackend
+
+
+
+
+/**
+ * performs one backend cycle.
+ *
+ * Collects all file descriptors from the active io objects which should be selected for reading and/or writing.
+ * Also determines the timer events which become due and adjusts the timeout.
+ * Constructs the necessary structures and calls poll().
+ * Finally interprets the results from poll() (i.e. performs the reading/writing/timer events)
+ *
+ * @param timeout maximal wait value in milliseconds; negative value waits until at least one event occured.
+ * @return @a true if there was at least one active object; otherwise @a false
+ *
+ * @note this method is a little beast.
+ *
+ * @internal
+ * The cycle is divided into four steps: collecting; poll; mark and execute.
+ * The "mark" step is necessary to avoid some bad side effects when method calls in the execution stage
+ * are calling @a Backup::doOneStep or open their own local backend loop.
+ *
+ * @todo handle some more error cases.
+ * @todo provide a plugin interface for external handler.
+ * (currently inclusion of external handler is possible by (ab)using timer classes)
+ */
+bool Backend::doOneStep(int timeout)
+{
+ ODOUT( "timeout=" << timeout );
+ internal_io::PollDataCluster poll_data;
+ bool had_active_object = false;
+
+ ++m_count_active_steps;
+
+ try {
+ // step 1 ; collect
+
+ { // step 1.1: collect fds for read/write operations
+ for(internal_io::IOList::iterator itIOList = internal_io::g_io_list().begin();
+ itIOList != internal_io::g_io_list().end();
+ ++itIOList)
+ {
+ if (! *itIOList) continue; // skip NULL entries
+ bool want_read = (*itIOList)->wantRead();
+ bool want_write = (*itIOList)->wantWrite();
+ int read_fd = (*itIOList)->m_read_fd;
+ int write_fd = (*itIOList)->m_write_fd;
+ if (!want_read && !want_write) continue;
+ if (want_read)
+ {
+ FODOUT( (*itIOList), "wants to read (fd=" << read_fd << ")");
+ poll_data.add_read_fd(read_fd, *itIOList);
+ }
+ if (want_write)
+ {
+ FODOUT( (*itIOList), "wants to write (fd=" << write_fd << ")");
+ poll_data.add_write_fd(write_fd, *itIOList);
+ }
+ had_active_object= true;
+ }
+ }
+
+ { // step 1.2: collect timer events
+ MilliTime current_time;
+ MilliTime min_event_time;
+
+ get_current_monotonic_time(current_time);
+ bool min_event_time_set;
+
+ if (timeout >= 0)
+ {
+ min_event_time = current_time + MilliTime(0,timeout);
+ min_event_time_set= true;
+ }
+ else
+ {
+ min_event_time = current_time + MilliTime(86400,0);
+ min_event_time_set= false;
+ }
+ // TODO
+
+ for(internal_io::TimerList::iterator it_timer= internal_io::g_timer_list().begin();
+ it_timer != internal_io::g_timer_list().end()
+ && (!had_active_object || !min_event_time_set || current_time < min_event_time);
+ ++ it_timer)
+ {
+ if (! *it_timer) continue; // skip NULL entries
+ if (! (*it_timer)->m_active) continue; // skip if not enabled
+ if ( !min_event_time_set || (*it_timer)->m_when < min_event_time)
+ {
+ min_event_time = (*it_timer)->m_when;
+ min_event_time_set= true;
+ }
+ had_active_object= true;
+ }
+
+ if (min_event_time_set)
+ { // we have at a minimal event time, so (re)compute the timeout value:
+ MilliTime delta= (min_event_time - current_time);
+ long long delta_ms = std::min( delta.get_milliseconds(), 21600000LL); // max 6h
+ if (delta_ms <= 0L)
+ {
+ timeout= 0L;
+ }
+ else
+ {
+ timeout= delta_ms + (delta_ms<5 ? 1 : 3);
+ }
+ }
+ }
+
+ // step 2 : poll
+ ODOUT(" poll timeout is " << timeout);
+ {
+ MilliTime current_time;
+ get_current_monotonic_time(current_time);
+ ODOUT(" current time is sec="<<current_time.mt_sec << ", msec=" << current_time.mt_msec);
+ }
+ int poll_result= ::poll(poll_data.get_pollfd_ptr(), poll_data.get_num_pollfds(), timeout);
+
+ ODOUT("poll -> " << poll_result);
+ {
+ MilliTime current_time;
+ get_current_monotonic_time(current_time);
+ ODOUT(" current time is sec="<<current_time.mt_sec << ", msec=" << current_time.mt_msec);
+ }
+
+ if (poll_result < 0)
+ {
+ //TODO poll error handling (signals ?!)
+ }
+
+ // step 3 : mark
+
+ // step 3.1: mark io objects (if necessary)
+ if (poll_result > 0)
+ {
+ for(internal_io::PollVector::iterator itPollItem = poll_data.m_poll_vector.begin();
+ itPollItem != poll_data.m_poll_vector.end();
+ ++itPollItem)
+ {
+ ODOUT(" fd=" << itPollItem->fd << ", events=" << itPollItem->events << ", revents=" << itPollItem->revents);
+ if ( 0 == (itPollItem->revents))
+ { // preliminary continuation if nothing is to handle for this item(/fd)...
+ continue;
+ }
+ if ( 0!= (itPollItem->revents & (POLLIN|POLLHUP)))
+ {
+ IOImplementation *io= poll_data.m_read_fd_io_map[ itPollItem->fd ];
+ if (io && io->m_read_fd==itPollItem->fd)
+ {
+ FODOUT(io,"marked for reading");
+ io->m_marked_for_reading= true;
+ }
+ }
+ if ( 0!= (itPollItem->revents & POLLOUT))
+ {
+ IOImplementation *io= poll_data.m_write_fd_io_map[ itPollItem->fd ];
+ if (io && io->m_write_fd==itPollItem->fd)
+ {
+ io->m_marked_for_writing= true;
+ }
+ }
+ if (0!= (itPollItem->revents & POLLERR))
+ {
+ IOImplementation *io= poll_data.m_write_fd_io_map[ itPollItem->fd ];
+ if (0!= (itPollItem->events & POLLOUT))
+ {
+ if (io && io->m_write_fd==itPollItem->fd)
+ {
+ io->m_marked_for_writing= false;
+ //io->close( Direction::out );
+ }
+ }
+ }
+ // TODO error handling (POLLERR, POLLHUP, POLLNVAL)
+ }
+ }
+
+ //Step 3.2: mark timer objects
+ {
+ MilliTime current_time;
+
+ get_current_monotonic_time(current_time);
+ ODOUT(" current time is sec="<<current_time.mt_sec << ", msec=" << current_time.mt_msec);
+
+ for(internal_io::TimerList::iterator it_timer= internal_io::g_timer_list().begin();
+ it_timer != internal_io::g_timer_list().end();
+ ++ it_timer)
+ {
+ ODOUT(" check timer " << *it_timer);
+ if (! *it_timer) continue; // skip NULL entries
+ if (! (*it_timer)->m_active) continue; // skip if not enabled
+ if ( (*it_timer)->m_when <= current_time)
+ {
+ ODOUT(" ==> MARK");
+ (*it_timer)->m_marked = true;
+ }
+ }
+ }
+
+
+ // step 4 : execute
+
+ // step 4.1: execute io
+ ODOUT("execute stage");
+ for(internal_io::IOList::iterator it_io = internal_io::g_io_list().begin();
+ it_io != internal_io::g_io_list().end();
+ ++ it_io)
+ {
+ ODOUT(" check obj " << *it_io);
+ if (NULL == *it_io) continue;
+ if ((*it_io)->m_marked_for_writing)
+ {
+ FODOUT((*it_io),"exec doWrite");
+ (*it_io)->doWrite();
+ if ((*it_io) == NULL) continue; // skip remaining if we lost the object
+ if ((*it_io)->m_errno)
+ {
+ continue;
+ }
+ }
+ if ((*it_io)->m_marked_for_reading)
+ {
+ FODOUT((*it_io),"exec doRead");
+ (*it_io)->doRead();
+ if ((*it_io) == NULL) continue; // skip remaining if we lost the object
+ if ((*it_io)->m_errno)
+ {
+ continue;
+ }
+ }
+ }
+
+ // step 4.2: execute timer events
+ {
+ for(internal_io::TimerList::iterator it_timer= internal_io::g_timer_list().begin();
+ it_timer != internal_io::g_timer_list().end();
+ ++ it_timer)
+ {
+ if (! *it_timer) continue; // skip NULL entries
+ if (! (*it_timer)->m_active) continue; // skip if not enabled
+ if (! (*it_timer)->m_marked) continue; // skip if not marked
+
+ // reset the mark and deactivate object now since the execute() method might activate it again
+ (*it_timer)->m_marked= false;
+ (*it_timer)->m_active= false;
+
+ // now execute the event:
+ (*it_timer)->execute();
+ }
+ }
+
+ } // eo try
+ catch(...)
+ {
+ // clean up our counter
+ --m_count_active_steps;
+ // and forward the exception
+ throw;
+ }
+
+ if ( 0 == --m_count_active_steps)
+ {
+ internal_io::g_io_list().clean_list();
+ internal_io::g_timer_list().clean_list();
+ }
+
+ return had_active_object;
+} // eo Backend::doOneStep
+
+
+/**
+ * enters a backend loop.
+ *
+ * Calls @a Backend::doOneStep within a loop until @a Backend::stop was called or there are no more
+ * active objects (io objects or timer objects).
+ */
+void Backend::run()
+{
+ ++m_count_active_loops;
+ do
+ {
+ try
+ {
+ if (!doOneStep(c_max_poll_wait))
+ {
+ // stop if there are no more active objects.
+ stop();
+ }
+ }
+ catch(...)
+ {
+ // clean up our counter
+ --m_count_active_loops;
+ // and forward the exception
+ throw;
+ }
+ }
+ while (0 == m_count_stop_requests);
+ --m_count_active_loops;
+ --m_count_stop_requests;
+} // eo Backend::run
+
+
+/**
+ * @brief stops the latest loop currently run by Backend::run().
+ * @see Backend::run()
+ */
+void Backend::stop()
+{
+ if (m_count_active_loops)
+ {
+ ++m_count_stop_requests;
+ }
+} // eo Backend::stop()
+
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
--- /dev/null
+/**
+ * @file
+ * @brief simple basic IO handling.
+ *
+ * @copyright © Copyright 2007-2008 by Intra2net AG
+ * @license commercial
+ * @contact info@intra2net.com
+ *
+ * Deals with POSIX file descriptors; provides an additional abstraction
+ * level above select() or poll() interface.
+ * Also provides basic functionality for dealing with timer events.
+ */
+
+#ifndef __I2N_SIMPLEIO_HPP__
+#define __I2N_SIMPLEIO_HPP__
+
+#include <string>
+#include <list>
+
+#include <pointer_func.hpp>
+
+#include <boost/signal.hpp>
+#include <boost/shared_ptr.hpp>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+/*
+ * forward declarations
+ */
+class Backend;
+class IOImplementation;
+
+/*
+ * end of forward declarations
+ */
+
+
+/**
+ * direction for io operations.
+ */
+struct Direction
+{
+ enum _Direction
+ {
+ unspecified= 0,
+ in = 1,
+ out = 2,
+ both= 3
+ } m_direction;
+
+ Direction( _Direction direction = unspecified) : m_direction(direction) {}
+
+ operator _Direction () const { return m_direction; }
+}; // eo struct IODirection;
+
+
+/**
+ * structure for storing (a point in time as) seconds and milliseconds.
+ */
+struct MilliTime
+{
+ long mt_sec;
+ long mt_msec;
+
+ MilliTime(long sec=0, long msec=0);
+
+ void set(long sec, long msec=0);
+
+ inline long long get_milliseconds() const
+ {
+ return ((long long)mt_sec * 1000L + mt_msec);
+ } // eo get_milliseconds
+
+ void normalize();
+
+ bool operator < (MilliTime& other);
+ bool operator == (MilliTime& other);
+
+ MilliTime& operator -= (const MilliTime& lhs);
+ MilliTime& operator += (const MilliTime& lhs);
+
+}; // eo struct MilliTime
+
+
+inline MilliTime operator + (const MilliTime& rhs, const MilliTime& lhs)
+{
+ MilliTime t(rhs);
+ return t+= lhs;
+} // eo operator + (const MilliTime& rhs, const MilliTime lhs)
+
+inline MilliTime operator - (const MilliTime& rhs, const MilliTime& lhs)
+{
+ MilliTime t(rhs);
+ return t-= lhs;
+} // eo operator - (const MilliTime& rhs, const MilliTime lhs)
+
+
+inline bool operator <= (MilliTime& rhs, MilliTime& lhs)
+{
+ return (rhs<lhs) || (rhs==lhs);
+} // eo operator <= (MilliTime& rhs, MilliTime& lhs)
+
+
+
+/**
+ * base class for time based events (timer events).
+ *
+ * consists basically of a point in time (when the event should be executed) and a method
+ * which will be called when the given time is reached (or passed).
+ */
+class TimerBase
+{
+ friend class Backend;
+ public:
+ TimerBase();
+ virtual ~TimerBase();
+
+ bool active() const { return m_active; }
+
+ MilliTime getWhenTime() const {return m_when;}
+ MilliTime getRealWhenTime() const;
+
+ protected:
+
+ void setWhenTime(long sec, long msec=0);
+ void setWhenTime(const MilliTime& mt);
+
+ void setDeltaWhenTime(long sec, long msec=0);
+ void setDeltaWhenTime(const MilliTime& mt);
+
+ void activate(bool _active= true);
+ void deactivate() { activate(false); }
+
+ virtual void execute();
+
+ private:
+ /// @a true when the event is active.
+ bool m_active;
+ /// point in time when the event should be executed
+ MilliTime m_when;
+ /// mark from backend cycle that the event has to be executed.
+ bool m_marked;
+}; // eo class TimerBase
+
+
+
+/**
+ * base class for filter classes.
+ *
+ * filter objects can be "plugged" into IO objects for manipulating the data streams.
+ * (for example: one could make a filter which handles the telnet protocol and plug it into a
+ * socket io object.)
+ *
+ * @note filter object can be used only by one io object.
+ */
+class FilterBase
+: virtual public SharedBase
+{
+ friend class IOImplementation;
+ public:
+ typedef boost::shared_ptr< FilterBase > PtrType;
+ public:
+ FilterBase();
+ virtual ~FilterBase() {};
+
+ protected:
+
+ virtual std::string filterIncomingData(const std::string& data)= 0;
+ virtual std::string filterOutgoingData(const std::string& data)= 0;
+
+ virtual void endOfIncomingData();
+ virtual void reset();
+
+ protected:
+
+ void injectIncomingData(const std::string& data);
+ void injectOutgoingData(const std::string& data);
+
+ private:
+ /// pointer to the io object which uses this filter:
+ IOImplementation *m_io;
+}; // eo class FilterBase
+
+typedef FilterBase::PtrType FilterBasePtr;
+
+
+/**
+ * identity filter; does nothing with the data (in other words: it's useless ;-) ).
+ */
+class FilterIdentity : public FilterBase
+{
+ protected:
+ virtual std::string filterIncomingData(const std::string& data) { return data; }
+ virtual std::string filterOutgoingData(const std::string& data) { return data; }
+}; // eo class FilterIdentity
+
+
+/**
+ * @brief null filter; deletes everything it receives.
+ *
+ * usefull when output from a subprocess (like stderr) should be ignored...
+ */
+class FilterNull : public FilterBase
+{
+ protected:
+ virtual std::string filterIncomingData(const std::string& data) { return std::string(); }
+ virtual std::string filterOutgoingData(const std::string& data) { return std::string(); }
+}; // eo FilterNull
+
+
+/**
+ * the base class of the IO classes.
+ *
+ * provides the functionality to read from a file desriptor and write to a file descriptor (which can be
+ * identical (like for socket io), but also can be different (like pipes from/to a process)).
+ * The data stream can be filtered through plugged filter objects which are building a filter chain.
+ * Incoming data is filtered forward through the chain; outgoing data is filtered backward through the chain.
+ * (So the first filter is the one which modifies the data closest to the connections).
+ *
+ * @note the functionality is provided in conjunction with the @a Backend class which handles parts of
+ * the low level IO.
+ *
+ * @note this is a base class; it provides most "interesting" functionality in the protected section only.
+ * This way, derived classes can decide which of that functionality they want to export in their own public
+ * interfaces and which to keep hidden.
+ */
+class IOImplementation
+: public boost::signals::trackable
+, virtual public SharedBase
+{
+ friend class Backend;
+ friend class FilterBase;
+
+ public:
+
+ typedef std::list< FilterBasePtr > FilterChain;
+
+ typedef boost::signal< void() > SignalType;
+
+ typedef boost::shared_ptr< IOImplementation > PtrType;
+
+ public:
+ IOImplementation(int read_fd=-1, int write_fd=-1);
+ virtual ~IOImplementation();
+
+ virtual void close(Direction direction = Direction::both);
+
+ virtual bool wantRead();
+ virtual bool wantWrite();
+
+ virtual bool opened() const;
+ virtual bool eof() const;
+ virtual bool writable() const;
+ virtual bool empty() const;
+
+ protected:
+
+ void addFilter(FilterBasePtr filter);
+ void removeFilter(FilterBasePtr);
+
+
+ void lowSend(const std::string& data);
+
+ std::string::size_type getOutputBufferSize() const { return m_output_buffer.size(); }
+
+ void setWriteFd(int fd);
+ void setReadFd(int fd);
+
+ inline int readFd() const
+ {
+ return m_read_fd;
+ }
+
+ inline int writeFd() const
+ {
+ return m_write_fd;
+ }
+
+
+ protected:
+ virtual void doRead();
+ virtual void doWrite();
+
+ void resetReadMark() { m_marked_for_reading= false; }
+ void resetWriteMark() { m_marked_for_writing= false; }
+
+ void injectIncomingData(FilterBasePtr from_filter, const std::string& _data);
+ void injectOutgoingData(FilterBasePtr from_filter, const std::string& _data);
+
+ protected:
+ /// last error number
+ int m_errno;
+ /// the input buffer (i.e. the data read from @a m_read_fd)
+ std::string m_input_buffer;
+
+ /// the chain of filter which are applied to the data received are the data which should be send.
+ FilterChain m_filter_chain;
+
+ /// signal which is fired when end of file is detected
+ SignalType m_signal_eof;
+ /// signal which is fired when write is no longer possible
+ SignalType m_signal_not_writable;
+ /// signal which is fired when new data was read
+ SignalType m_signal_read;
+ /// signal which is fired when data was written
+ SignalType m_signal_write;
+
+ /// end of file (on @a m_read_fd) detected (used additionally when m_read_fd is valid)
+ bool m_eof;
+
+ /// unable-to-write (on @a m_write_fd) detected (used additionally when m_write_fd is valid)
+ bool m_not_writable;
+
+ private:
+ /// the file descriptor to read from (-1 if none is given)
+ int m_read_fd;
+ /// the file descriptor to write to (-1 if none is given)
+ int m_write_fd;
+ /// output buffer; contains the data which needs to be written.
+ std::string m_output_buffer;
+
+ private:
+ /// @a true when data is available to be read
+ bool m_marked_for_reading;
+ /// @a true when data can be written
+ bool m_marked_for_writing;
+
+}; // eo class IOImplementation
+
+
+
+/**
+ * same as IOImplementation, but makes fd access functions public.
+ */
+class IOImplementation2 : public IOImplementation
+{
+ typedef IOImplementation inherited;
+ public:
+ IOImplementation2(int read_fd=-1, int write_fd=-1) : inherited(read_fd, write_fd) {}
+
+ void setWriteFd(int fd) { inherited::setWriteFd(fd); }
+ void setReadFd(int fd) { inherited::setReadFd(fd); }
+
+ int readFd() const { return inherited::readFd(); }
+ int writeFd() const { return inherited::writeFd(); }
+
+}; // eo class IOImplementation2
+
+
+/**
+ * provides sending data and receiving data via a signal.
+ *
+ * @note the received data is passed as parameter to the signal and no longer stored in the received buffer.
+ */
+class SimpleIO : public IOImplementation
+{
+ typedef IOImplementation inherited;
+ public:
+ SimpleIO(int read_fd=-1, int write_fd=-1);
+ virtual ~SimpleIO();
+
+ void sendString(const std::string& data);
+
+ boost::signal<void(const std::string&)> signal_received_string;
+
+ private:
+
+ void slotReceived();
+}; // eo class SimpleIO
+
+
+/**
+ * provides sending data and receiving data via a signal.
+ *
+ * @note the received data is passed as parameter to the signal and no longer stored in the received buffer.
+ */
+class SimpleIO2 : public IOImplementation2
+{
+ typedef IOImplementation2 inherited;
+ public:
+ SimpleIO2(int read_fd=-1, int write_fd=-1);
+ virtual ~SimpleIO2();
+
+ void sendString(const std::string& data);
+
+ boost::signal<void(const std::string&)> signal_received_string;
+
+ private:
+
+ void slotReceived();
+}; // eo class SimpleIO2
+
+
+/**
+ * provides the backend for io handling.
+ *
+ * This (singleton) object provides the management of io events. It collects all wishes for reading/writing
+ * from the io objects and information from the timer events.
+ * It poll()s for the events and distributes them to the objects,
+ *
+ * The class provides the possibility for executing one io cycle (/step) or to run a backend loop.
+ *
+ * @note the Backend class needs to be a friend of IOImplementation since it accesses private members
+ * of IOImplementation while performing the io cycles.
+ */
+class Backend
+{
+ public:
+
+ bool doOneStep(int ms_timeout= -1);
+ void run();
+ void stop();
+
+ protected:
+ Backend();
+ Backend(const Backend& other);
+ ~Backend();
+
+ protected:
+
+ /// the number of currently active backend loops
+ int m_count_active_loops;
+ /// the number of pending stop requests (where each one should exit one active backend loop)
+ int m_count_stop_requests;
+
+ /// the number of currently active backend cycles(/ steps)
+ static int m_count_active_steps;
+
+ public:
+ static Backend* getBackend();
+
+ protected:
+ /// pointer to the active backend (which is delivered by Backend::getBackend)
+ static Backend* g_backend;
+}; // eo class Backend
+
+
+
+/*
+** tool functions:
+*/
+
+
+void get_current_real_time(MilliTime& mt);
+void get_current_monotonic_time(MilliTime& mt);
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
+
+#endif
--- /dev/null
+/** @file
+ *
+ * (c) Copyright 2007 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+#include "simplepipe.hpp"
+
+#include <functional>
+#include <boost/bind.hpp>
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <errno.h>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+/*
+ * Implementation of SimplePipe
+ */
+
+SimplePipe::SimplePipe()
+{
+ m_signal_read.connect(boost::bind(&SimplePipe::slotReceived,this));
+} // eo SimplePipe::SimplePipe()
+
+
+SimplePipe::~SimplePipe()
+{
+} // eo SimplePipe::~SimplePipe()
+
+
+/**
+ * makes a pipe.
+ * This method connects itself with a newly created peer object with a bidirectional pipe.
+ * @return the peer pipe object.
+ */
+bool SimplePipe::makePipe(SimplePipe& peer)
+{
+ close(); // just in case...
+
+ int fds[2];
+
+ int res= ::socketpair( AF_UNIX, SOCK_STREAM, 0, fds);
+
+ if (res)
+ {
+ m_errno= errno;
+ return false;
+ }
+ else
+ {
+ m_errno= 0;
+ }
+
+ peer.close(); // just in case
+
+ setWriteFd(fds[0]);
+ setReadFd(fds[0]);
+
+ peer.setWriteFd(fds[1]);
+ peer.setReadFd(fds[1]);
+
+ return true;
+} // eo SimplePipe.:makePipe()
+
+
+/**
+ * sends a string through the pipe.
+ * @param data the data which should be sent to the other side.
+ */
+void SimplePipe::sendString(const std::string& data)
+{
+ lowSend(data);
+} // eo SimplePipe::sendString(const std::string&)
+
+
+/**
+ * emits the signal signalReceived with the received data.
+ * This slot is connected to IOImplementation::m_signal_read.
+ */
+void SimplePipe::slotReceived()
+{
+ std::string data;
+ data.swap(m_input_buffer);
+ signal_received_string(data);
+} // eo SimplePipe::slotReceived()
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
--- /dev/null
+/** @file
+ *
+ * (c) Copyright 2007 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+#ifndef _SIMPLEIO_SIMPLEPIPE_HPP_
+#define _SIMPLEIO_SIMPLEPIPE_HPP_
+
+#include "simpleio.hpp"
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+class SimplePipe : public IOImplementation
+{
+ public:
+ SimplePipe();
+ virtual ~SimplePipe();
+
+ bool makePipe(SimplePipe& peer);
+
+ void sendString(const std::string& data);
+
+ boost::signal<void(const std::string&)> signal_received_string;
+
+ protected:
+
+ private:
+
+ void slotReceived();
+
+}; // eo SimplePipe
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
+
+
+#endif
--- /dev/null
+/** @file
+ *
+ *
+ * (c) Copyright 2007-2008 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+//#define NOISEDEBUG
+
+#include "simpleprocess.hpp"
+
+#include <iterator>
+#include <algorithm>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+#include <filefunc.hxx>
+
+
+#ifdef NOISEDEBUG
+#include <iostream>
+#include <iomanip>
+#define DOUT(msg) std::cout << msg << std::endl
+#define FODOUT(obj,msg) std::cout << typeid(*obj).name() << "[" << obj << "]:" << msg << std::endl
+#define ODOUT(msg) std::cout << typeid(*this).name() << "[" << this << "]:" << msg << std::endl
+#else
+#define DOUT(msg) do {} while (0)
+#define FODOUT(obj,msg) do {} while (0)
+#define ODOUT(msg) do {} while (0)
+#endif
+
+
+namespace
+{
+
+using namespace I2n::SimpleIo;
+
+/**
+ * local configuration values
+ */
+namespace config
+{
+
+ /// the capacity of the child status list (/ vector)
+ const unsigned int pid_pool_capacity= 512;
+
+} // eo namespace config
+
+
+
+/// the previous handler for the child signal (SIGCHLD)
+void (*oldChildHandler)(int) = NULL;
+
+/// method pointer for activating process manager
+void (ProcessManager::*_activate_manager)();
+
+PidStateList pending_pid_states;
+
+
+/**
+ * signal handler for child signal (SIGCHLD)
+ * @param sig the signal number as provided by the OS
+ */
+void handleSigChild(int sig)
+{
+ int status;
+ pid_t pid;
+ while ( (pid = waitpid(-1,&status,WNOHANG)) > 0)
+ {
+ pending_pid_states.push_back( PidStatePair(pid,status) );
+ }
+ if (_activate_manager)
+ {
+ // tricky way to access a protected method without being a (official) friend:
+ ( ProcessManager::getInstance()->*_activate_manager)();
+ }
+ //TODO: ?
+ signal(sig,handleSigChild);
+} // eo handleSigChild
+
+
+namespace process
+{
+
+typedef std::pair<pid_t, ProcessImplementation*> PidProcPair;
+typedef std::list< PidProcPair > PidProcList;
+
+
+template< typename F, typename S >
+struct CmpFirst
+{
+ F _f;
+ CmpFirst ( F f ) : _f(f) {}
+ bool operator () ( const std::pair<F,S>& v ) const { return v.first == _f; }
+}; // eo struct CmpFirst
+
+
+std::list<ProcessImplementation*> g_process_list;
+PidProcList g_pid_list;
+
+
+void addProcessInstance( ProcessImplementation* obj )
+{
+ g_process_list.push_back(obj);
+} // eo addProcessInstance(ProcessImplementation*)
+
+
+void removeProcessInstance( ProcessImplementation* obj )
+{
+ // remove obj from list
+ g_process_list.remove(obj);
+ // clear pointers in pid list
+ for(PidProcList::iterator it= g_pid_list.begin();
+ it != g_pid_list.end();
+ ++it)
+ {
+ if (it->second == obj)
+ {
+ it->second= NULL;
+ }
+ }
+} // eo removeProcessInstance(ProcessImplementation*)
+
+
+void addChildProcess( pid_t pid, ProcessImplementation* obj)
+{
+ g_pid_list.push_back ( PidProcPair(pid,obj) );
+} // eo addChildProcess(pid_t,ProcessImplementation*)
+
+
+void removeChildProcess ( pid_t pid, ProcessImplementation* obj)
+{
+ PidProcList::iterator it= std::find(
+ g_pid_list.begin(), g_pid_list.end(),
+ PidProcPair(pid,obj));
+ if (it != g_pid_list.end())
+ {
+ g_pid_list.erase(it);
+ }
+} // eo removeChildProcess(pid_t,ProcessImplementation*)
+
+
+bool findChildProcess ( pid_t pid, ProcessImplementation* & obj )
+{
+ PidProcList::iterator it = std::find_if(
+ g_pid_list.begin(), g_pid_list.end(),
+ CmpFirst<pid_t,ProcessImplementation*>(pid) );
+ if (it == g_pid_list.end())
+ {
+ return false;
+ }
+ obj = it->second;
+ return true;
+} // eo findChildProcess(pid_t,ProcessImplementation*&)
+
+
+} // eo namespace process
+
+
+
+
+
+/*
+** misc tools
+*/
+
+
+/**
+ * convenience tool for closing file descriptors...
+ */
+struct FdCloser
+{
+ int m_fd;
+
+ FdCloser(int fd=-1) : m_fd(fd) {}
+
+ ~FdCloser()
+ {
+ if (m_fd >= 0) ::close(m_fd);
+ }
+
+ void release() { m_fd= -1; }
+
+}; // eo struct FdCloser
+
+
+
+} // eo namespace <anonymous>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+/*
+ * global functions
+ */
+
+/**
+ * installs the handler for the child signal (SIGCHLD).
+ * Installing this handler is mandatory for the process subsystem to work correctly.
+ * @return @a true iff the child handler is successfully installed.
+ */
+bool installChildHandler()
+{
+ if (oldChildHandler)
+ {
+ // already installed
+ return true;
+ }
+ if (! ProcessManager::getInstance() )
+ {
+ // we need an instance of the process manager
+ return false;
+ }
+ pending_pid_states.reserve( config::pid_pool_capacity );
+ oldChildHandler = signal( Signal::CHLD, handleSigChild );
+ if (oldChildHandler == SIG_ERR)
+ {
+ oldChildHandler= NULL;
+ return false;
+ }
+ return true;
+} // eo installChildHandler
+
+
+/**
+ * uninstalls the child handler.
+ * @return @a true iff the old child handler is reestablished.
+ */
+bool restoreChildHandler()
+{
+ if (!oldChildHandler)
+ {
+ return false;
+ }
+ void(*res)(int) = signal( Signal::CHLD, oldChildHandler);
+
+ if (res == SIG_ERR)
+ {
+ return false;
+ }
+ oldChildHandler= NULL;
+ return true;
+} // eo restoreChildHandler
+
+
+
+
+/*
+ * Implementation of ProcessImplementation
+ */
+
+IOImplementation2* ProcessImplementation::_StderrOnStdout = ((IOImplementation2*) 1);
+IOImplementation2* ProcessImplementation::_UseParentsStderr = ((IOImplementation2*) 0);
+
+
+/**
+ * @brief constructor for the process implementation.
+ *
+ * the constructor takes the path to the executable and (initial) cli arguments.
+ *
+ * @param path path to the executable.
+ * @param args initial command line arguments.
+ */
+ProcessImplementation::ProcessImplementation(
+ const std::string& path,
+ const std::vector<std::string>& args
+ )
+: IOImplementation(-1,-1)
+, m_path(path)
+, m_nice_inc(0)
+, m_create_new_session(false)
+, m_pid(0)
+, m_state(ProcessState::stopped)
+, m_exit_code(0)
+{
+ m_args.push_back(path);
+ std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
+ process::addProcessInstance(this);
+} // eo ProcessImplementation::ProcessImplementation(const std::string&)
+
+
+ProcessImplementation::~ProcessImplementation()
+{
+ if (m_pid > 0 && m_state!=ProcessState::stopped)
+ {
+ stopProcess(true);
+ }
+ process::removeProcessInstance(this);
+} // eo ProcessImplementation::~ProcessImplementation()
+
+
+void ProcessImplementation::close(Direction direction)
+{
+ inherited::close(direction);
+ if (!inherited::opened() && (m_state != ProcessState::stopped) )
+ {
+ stopProcess(false);
+ }
+} // eo ProcessImplementation::close(Direction)
+
+
+/**
+ * returns an object for adding new arguments to the argument list.
+ * @return the adder object.
+ */
+PushBackFiller<std::string, std::vector > ProcessImplementation::getArgAdder()
+{
+ return PushBackFiller<std::string, std::vector >(m_args);
+} // eo ProcessImplementation::getArgAdder()
+
+
+/**
+ * @brief set if the process should create a new session when started.
+ * @param enable determine if the process should start a new session.
+ * @return @a true iff the value of enable was accepted.
+ *
+ * If the process is already running, a new value is not accepted.
+ */
+bool ProcessImplementation::setCreateNewSession( bool enable )
+{
+ if (m_state != ProcessState::stopped and enable != m_create_new_session)
+ {
+ return false;
+ }
+ m_create_new_session= enable;
+ return true;
+} // eo ProcessImplementation::setCreateNewSession(bool);
+
+
+/**
+ * @brief sets a new nice increment.
+ * @param nice the desired nice increment.
+ * @return @a true if the value was accepted and - in case the process was already started -
+ * the nice value was successfully changed.
+ */
+bool ProcessImplementation::setNice(int nice)
+{
+ errno= 0;
+ if (m_state != ProcessState::stopped)
+ {
+ int delta= m_nice_inc + nice;
+ m_nice_inc= nice;
+ int res= ::nice(delta);
+ if (res == -1 and errno !=0 )
+ {
+ return false;
+ }
+ }
+ else
+ {
+ m_nice_inc = nice;
+ }
+ return true;
+} // eo ProcessImplementation::setNice(int)
+
+
+/**
+ * @brief sets the work dir the process should be started with.
+ * @param workdir the workdir
+ * @return @a true if the new workdir was accepted.
+ *
+ * The method will return @a false if the process is already started.
+ * The workdir can only be set before the process is started.
+ */
+bool ProcessImplementation::setWorkDir(const std::string& workdir)
+{
+ if ( m_state != ProcessState::stopped and workdir != m_workdir)
+ {
+ return false;
+ }
+ if (not workdir.empty())
+ {
+ I2n::Stat stat(workdir);
+ if (not stat or not stat.is_directory())
+ {
+ return false;
+ }
+ }
+ m_workdir= workdir;
+ return true;
+} // eo ProcessImplementation::setWorkDir(const std::string&)
+
+
+/**
+ * @brief sets new arguments for the process (the path to the binary is kept).
+ *
+ * @param args the new cli arguments for the subprocess (replacing the old ones).
+ */
+void ProcessImplementation::resetArgs( const std::vector< std::string >& args )
+{
+ if (m_args.size() > 1)
+ {
+ m_args.erase( ++m_args.begin(), m_args.end());
+ }
+ std::copy( args.begin(), args.end(), std::back_inserter(m_args) );
+} // eo ProcessImplementation::resetArgs(const std::vectors< std::string >&)
+
+
+/**
+ * starts the new process.
+ * provides pipes for sending data to/ receiving data from the new process.
+ * Basically forks and execs the new process.
+ *
+ * @param stderr if not NULL the given object will be connected to stderr of the new process.
+ * The object can then be used for reading the data from the process' stderr; but cannot be written to.
+ * (The object will be closed if it was open).
+ * If the constant @a ProcessImplementation::StderrOnStdout is passed then stderr of the new process will
+ * be written to the same channel as stdout (i.e. can be read from the process class instance like the
+ * normal output).
+ * If NULL then the stderr channel from the parent process will also be used by the child.
+ * @return @a true iff the new subprocess started.
+ */
+bool ProcessImplementation::startProcess( IOImplementation2 *stderr )
+{
+ bool stderr2stdout= false;
+ m_errno = 0;
+ m_input_buffer.clear();
+ if (m_pid > 0 && m_state != ProcessState::stopped)
+ {
+ // process still/already running...
+ return false;
+ }
+ m_exit_code= 0;
+
+ if (stderr == _StderrOnStdout)
+ {
+ stderr2stdout= true;
+ stderr= NULL;
+ }
+
+ int to_process_pipe[2];
+ int from_process_pipe[2];
+ int from_process_stderr_pipe[2]= { -1, -1 };
+
+ if ( ::pipe(to_process_pipe) )
+ {
+ m_errno= errno;
+ return false;
+ }
+ FdCloser closeTo0( to_process_pipe[0] );
+ FdCloser closeTo1( to_process_pipe[1] );
+ if ( ::pipe (from_process_pipe) )
+ {
+ m_errno= errno;
+ return false;
+ }
+ FdCloser closeFrom0( from_process_pipe[0] );
+ FdCloser closeFrom1( from_process_pipe[1] );
+ if (stderr)
+ {
+ if (stderr->opened()) stderr->close();
+ if ( ::pipe (from_process_stderr_pipe) )
+ {
+ m_errno= errno;
+ return false;
+ }
+ }
+ FdCloser closeFromErr0( from_process_stderr_pipe[0] );
+ FdCloser closeFromErr1( from_process_stderr_pipe[1] );
+
+ m_pid = ::fork();
+
+ if ( m_pid == (pid_t)-1 )
+ {
+ m_errno= errno;
+ m_pid= 0;
+ // error; something went wrong
+ return false;
+ }
+ else if (m_pid > 0)
+ {
+ // we are in the parent part
+
+ // keep the fd's we need and (later) close the other ones:
+ closeTo1.release(); // don't close this fd!
+ setWriteFd(to_process_pipe[1]);
+ closeFrom0.release(); // don't close this fd!
+ setReadFd(from_process_pipe[0]);
+
+ if (stderr)
+ {
+ closeFromErr0.release(); // don't close this fd!
+ stderr->setReadFd(from_process_stderr_pipe[0]);
+ }
+
+ m_state= ProcessState::running;
+ process::addChildProcess(m_pid,this);
+ DOUT(" started child with pid " << m_pid);
+ return true;
+ }
+ else // pid > 0
+ {
+ // we are in the child part
+
+ // dup the fd's for stdin/-out/-err into place:
+ ::dup2(to_process_pipe[0],0);
+ ::dup2(from_process_pipe[1],1);
+ if (stderr)
+ {
+ ::dup2(from_process_stderr_pipe[1],2);
+ ::close(from_process_stderr_pipe[0]); ::close(from_process_stderr_pipe[1]);
+ }
+ else if (stderr2stdout)
+ {
+ ::dup2(from_process_pipe[1],2);
+ }
+ // close what we don't need:
+ ::close(to_process_pipe[0]); ::close(to_process_pipe[1]);
+ ::close(from_process_pipe[0]); ::close(from_process_pipe[1]);
+
+ // set workdir if requested:
+ if (not m_workdir.empty())
+ {
+ int r= ::chdir( m_workdir.c_str() );
+ if (r !=0 )
+ {
+ //TODO?
+ exit(255);
+ }
+ }
+
+ //
+ // collect args:
+ char **argv= new char*[m_args.size()+1];
+ int i=0;
+ for(std::vector<std::string>::iterator it= m_args.begin();
+ it != m_args.end();
+ ++it,++i)
+ {
+ argv[i]= strdup( it->c_str() );
+ }
+ argv[i]= NULL;
+ // update nice level:
+ if (m_nice_inc)
+ {
+ nice(m_nice_inc);
+ }
+ // create a new session id if requested:
+ if (m_create_new_session)
+ {
+ setsid();
+ }
+ // execute:
+ execv(m_path.c_str(), argv);
+ // exit if exec failed
+ exit(255);
+ //cleanup! ... just joking; we exec or we exit, in either case the system cleans
+ // everything which needs to be cleaned up.
+ }
+ return false; // keep the compiler happy...
+} // eo ProcessImplementation::startProcess()
+
+
+/**
+ * convenience method for starting the child process.
+ * This method uses predefined enum values for the stderr handling mode.
+ *
+ * @param stderr_mode the desired stderr mode.
+ * @return @a true iff the child process was created.
+ */
+bool ProcessImplementation::startProcess( ProcessImplementation::StderrMode stderr_mode )
+{
+ switch (stderr_mode)
+ {
+ case UseParentsStderr:
+ return startProcess( _UseParentsStderr );
+
+ case StderrOnStdout:
+ return startProcess( _StderrOnStdout );
+ }
+ return false;
+}; // eo ProcessImplementation::startProcess(ProcessImplementation::StderrMode)
+
+
+/**
+ * stops the process.
+ *
+ * @todo think about a more intelligent handling...
+ */
+void ProcessImplementation::stopProcess(bool force)
+{
+ // TODO: do it somewhat more intelligent?!
+ if (force)
+ {
+ kill(Signal::KILL);
+ //TODO: set running state?
+ }
+ else
+ {
+ kill(Signal::TERM);
+ }
+} // eo ProcessImplementation::stop(bool)
+
+
+
+/**
+ * sends a signal to the child process.
+ * @param signal the Signal which should be send.
+ * @return @a true if the signal was sent; @a false if an error occured.
+ */
+bool ProcessImplementation::kill(Signal signal)
+{
+ m_errno = 0;
+ if (m_pid == 0 || m_pid == (pid_t)-1)
+ {
+ m_errno= ESRCH;
+ return false;
+ }
+ int res = ::kill(m_pid, signal);
+ if (res < 0)
+ {
+ m_errno= errno;
+ return false;
+ }
+ if (signal == Signal::CONT && m_state == ProcessState::suspended)
+ {
+ m_state = ProcessState::running;
+ }
+ return true;
+} // eo ProcessImplementation::kill(Signal)
+
+
+
+/**
+ * set a new child state with information gobbled by the child signal handler.
+ *
+ * @note This method should only be called by the process manager!
+ *
+ * @param pid the pid of the child process.
+ * @param status the new status value (as delivered by waitpid())
+ */
+void ProcessImplementation::setChildState(pid_t pid, int status)
+{
+ DOUT("setChildState("<<pid<<","<<status<<") pid="<<m_pid);
+ if (pid != m_pid)
+ {
+ // old child... ignore!
+ return;
+ }
+ if (WIFSTOPPED(status))
+ {
+ DOUT("stopped");
+ // stopped:
+ int stopsignal = WSTOPSIG(status);
+ // make stop signal available in exit_code:
+ m_exit_code= (stopsignal << 8);
+ m_state= ProcessState::suspended;
+ return;
+ }
+#ifdef WIFCONTINUED
+ if (WIFCONTINUED(status))
+ {
+ DOUT("continued");
+ // continued after a stop:
+ m_state= ProcessState::running;
+ return;
+ }
+#endif
+ if (WIFEXITED(status))
+ {
+ DOUT("normal exit");
+ //normal exit:
+ m_exit_code= (0xff & WEXITSTATUS(status));
+ m_pid= 0;
+ close(Direction::out);
+ m_state= ProcessState::stopped;
+ m_signal_terminated();
+ return;
+ }
+ if (WIFSIGNALED(status))
+ {
+ DOUT("signaled stop");
+ // exit by signal:
+ int termsignal = WTERMSIG(status);
+ // make term signal available in exit code (normal exit codes are only 8 bit)
+ m_exit_code = (termsignal << 8);
+ m_pid= 0;
+ close(Direction::out);
+ m_state= ProcessState::stopped;
+ m_signal_terminated();
+ return;
+ }
+ // this point should never be reached...!!
+} // eo ProcessImplementation::setChildState(pid_t,int)
+
+
+/*
+ * implementation of ProcessManager
+ */
+
+/// the instance of the process manager (highlander; there can be only one!)
+ProcessManager* ProcessManager::the_instance= NULL;
+
+
+ProcessManager::ProcessManager()
+{
+ setWhenTime(0);
+} // eo ProcessManager::ProcessManager
+
+
+/**
+ * delivers the process manager instance (generate if it doesn't exist)
+ * @return the process manager instance
+ */
+ProcessManager* ProcessManager::getInstance()
+{
+ if (! the_instance)
+ {
+ the_instance = new ProcessManager();
+ _activate_manager = &ProcessManager::activateMe;
+ }
+ return the_instance;
+} // eo ProcessManager::getInstance
+
+
+/**
+ * activate the timer so it's handled by the next backend cycle
+ */
+void ProcessManager::activateMe()
+{
+ setWhenTime(0);
+ activate();
+} // eo ProcessManager::activateMe
+
+
+/**
+ * real work is done here.
+ * Processes the information collected by the child signal handler.
+ */
+void ProcessManager::execute()
+{
+ PidStateList pid_state_list;
+ {
+ // block child signals (within this scope)
+ ScopedSignalBlocker blocker( Signal::CHLD );
+ // and now fetch the list of pending information
+ // (simply swap with our local empty list)
+ std::swap(pid_state_list, pending_pid_states);
+ // reserve the desired (minimum) capacity
+ pending_pid_states.reserve( config::pid_pool_capacity );
+ }
+ ODOUT("exec, " << pid_state_list.size() << " entries");
+
+ // interpret states:
+ for(PidStateList::iterator it = pid_state_list.begin();
+ it != pid_state_list.end();
+ ++it)
+ {
+ pid_t pid = it->first;
+ int status = it->second;
+ ODOUT(" pid=" << pid << ", status=" << status);
+ ProcessImplementation *process_obj;
+ if (process::findChildProcess(pid,process_obj))
+ {
+ ODOUT(" local managed child, process_obj="<< process_obj);
+ // pid found in list:
+ if (!WIFSTOPPED(status)
+#ifdef WIFCONTINUED
+ && !WIFCONTINUED(status)
+#endif
+ )
+ {
+ // take it from list if the child exited:
+ process::removeChildProcess(pid,process_obj);
+ }
+ if (process_obj)
+ {
+ // give the process object a chance to handle the state change:
+ process_obj->setChildState(pid, status);
+ }
+ }
+ else
+ {
+ ODOUT("foreign child");
+ // pid not found in list:
+ /* NOTE: in a non threaded environment this pid must be from a child process which is not
+ managed by this process classes; since this method is called after all setup of a child process
+ is done (; especially entering the new child pid into our internal lists).
+ */
+ m_foreign_pid_states.push_back(*it);
+ }
+ }
+
+ // handle the foreign childs:
+ {
+ /* idea:
+ * fetch a (pid,status) from the list, erase it (to avoid reentrance problems)
+ * and fire the signal. If someone forks childs outside this module then he can
+ * connect to the signal and receive all necessary status information gobbled by
+ * our child handler.
+ */
+ while (! m_foreign_pid_states.empty())
+ {
+ PidStateList::iterator it= m_foreign_pid_states.begin();
+ pid_t pid = it->first;
+ int status = it->second;
+ m_foreign_pid_states.erase(it);
+ m_foreign_child_state_changed_signal(pid,status);
+ }
+ }
+
+} // eo ProcessManager::execute
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
--- /dev/null
+/** @file
+ *
+ * simple process handling based on simple io classes.
+ *
+ * (c) Copyright 2007-2008 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+#ifndef _CONND_SIMPLEPROCESS_HPP_
+#define _CONND_SIMPLEPROCESS_HPP_
+
+#include <vector>
+#include <utility>
+
+#include <sys/types.h>
+
+#include <containerfunc.hpp>
+#include <signalfunc.hpp>
+#include "simpleio.hpp"
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+using SystemTools::Signal;
+using SystemTools::ScopedSignalBlocker;
+
+
+class ProcessManager;
+
+
+typedef std::pair< pid_t, int > PidStatePair;
+typedef std::vector< PidStatePair > PidStateList;
+
+
+/**
+ * represents process states.
+ */
+struct ProcessState
+{
+ enum _ProcessState
+ {
+ stopped = 0,
+ running,
+ suspended
+ }; // eo enum _ProcessState
+
+ _ProcessState m_state;
+
+ ProcessState(_ProcessState _state = stopped) : m_state(_state) {}
+
+ operator _ProcessState() const { return m_state; }
+}; // eo struct ProcessState
+
+
+/**
+ * specialisation of the io implementation class which fork/exec's a subprocess and
+ * connects with the new child's stdin/stdout.
+ *
+ * @note the signal @a IOImplementation::m_signal_eof of the base class can be used to detect when the
+ * new child closes it's stdout (which usually means that the child ended).
+ */
+class ProcessImplementation : public IOImplementation
+{
+ typedef IOImplementation inherited;
+
+ friend class ProcessManager;
+
+ public:
+
+ enum StderrMode
+ {
+ /// "magic" constant to pass to start() when childs stderr should be the same as parents stderr.
+ UseParentsStderr= 0,
+ /// "magic" constant to pass to start() when stderr should be the same as stdout for the new process.
+ StderrOnStdout
+ }; // eo enum StderrMode
+
+ public:
+ ProcessImplementation(
+ const std::string& path,
+ const std::vector<std::string>& args = std::vector<std::string>());
+ virtual ~ProcessImplementation();
+
+ virtual void close(Direction direction = Direction::both);
+
+ virtual bool startProcess( IOImplementation2* stderr );
+ bool startProcess( StderrMode stderr_mode = UseParentsStderr );
+
+ virtual void stopProcess(bool force=false);
+
+ PushBackFiller<std::string, std::vector > getArgAdder();
+
+ bool setCreateNewSession( bool enable= true);
+
+ bool setNice(int nice);
+
+ bool setWorkDir(const std::string& workdir);
+
+ void resetArgs( const std::vector< std::string >& args = std::vector< std::string >() );
+
+ /// returns the current process state
+ ProcessState processState() const { return m_state; }
+
+ ///returns the exit code of the process (if in stopped state)
+ int exitCode() const { return m_exit_code; }
+
+
+ protected:
+
+ bool kill(const Signal signal);
+
+ void setChildState(pid_t pid, int status);
+
+ protected:
+ /// the path to the binary
+ std::string m_path;
+ /// argument list (starting with argv0, usually the name of the binary)
+ std::vector<std::string> m_args;
+ /// increment of the nice level when the new child is started
+ int m_nice_inc;
+ /// determines if the child should start a new session.
+ bool m_create_new_session;
+ /// determines the workdir where the child process should be started with.
+ std::string m_workdir;
+
+ /// the pid of the child process
+ pid_t m_pid;
+ /// the state of the child process
+ ProcessState m_state;
+ /// the exit code of the child (-1 if not available yet)
+ int m_exit_code;
+
+ /// signal which is fired when the child terminated
+ SignalType m_signal_terminated;
+
+
+ /// "magic" constant to pass to start() when childs stderr should be the same as parents stderr.
+ static IOImplementation2* _UseParentsStderr;
+ /// "magic" constant to pass to start() when stderr should be the same as stdout for the new process.
+ static IOImplementation2* _StderrOnStdout;
+
+ private:
+
+}; // eo class ProcessImplementation
+
+
+/**
+ * manages overall process related stuff.
+ *
+ * @note this class is implemented as a singleton.
+ * @note this class uses the io timer interface to be called within the backend loops when necessary.
+ */
+class ProcessManager : public TimerBase
+{
+ public:
+
+ static ProcessManager* getInstance();
+
+ protected:
+ ProcessManager();
+ ProcessManager(const ProcessManager&);
+
+ virtual void execute();
+
+ void activateMe();
+
+ public:
+
+ /**
+ * the signal which is fired when waitpid() returns a status for a child process
+ * which is not managed by this process subsystem.
+ * Another module which forks child processes can connect to this signal to receive
+ * the information when these child processes are terminated.
+ */
+ boost::signal<void(pid_t,int)> m_foreign_child_state_changed_signal;
+
+ protected:
+
+ static ProcessManager *the_instance;
+
+ PidStateList m_foreign_pid_states;
+
+ private:
+};
+
+
+bool installChildHandler();
+bool restoreChildHandler();
+
+
+} // eo namespace SimpleIo
+} // eo I2n
+
+#endif
--- /dev/null
+/** @file
+ *
+ * (c) Copyright 2008 by Intra2net AG
+ *
+ * info@intra2net.com
+ *
+ * @todo unlink unix server socket on close.
+ */
+
+#include "simplesocket.hpp"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <errno.h>
+#include <sys/un.h>
+#include <netinet/in.h>
+#include <limits.h>
+#include <filefunc.hxx>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+namespace
+{
+
+struct sockaddr_un dummy_un;
+
+union MegaAddr {
+ struct sockaddr m_addr;
+ struct sockaddr_in m_addr_in;
+ struct sockaddr_un m_addr_un; // NOTE (historically) too small...
+ // storage is large enough to hold all sockaddr_* variants with the (historical) exception of _un !
+ struct sockaddr_storage m_addr_store;
+ // a char array large enough to hold _un (with an path up to the maximum allowed size!)
+ // (the +1 is added for a later 0-termination of the path)
+ char m_buffer[ sizeof(dummy_un) - sizeof(dummy_un.sun_path) + PATH_MAX + 1 ];
+};
+
+
+} // eo namespace <anonymous>
+
+
+
+/*
+** implementation of ServerSocketBaseImplementation
+*/
+
+
+ServerSocketBaseImplementation::ServerSocketBaseImplementation()
+: IOImplementation()
+{
+} // eo ServerSocketBaseImplementation::ServerSocketBaseImplementation()
+
+
+/**
+ * @brief handled incoming connections on the server port.
+ *
+ * accepts the new connection, stores the peer address in an internal buffer
+ * and calls a (derived) acceptNewConnection method to create an apropriate
+ * IO class instance.
+ * If no io instance is created the connection is closed.
+ */
+void ServerSocketBaseImplementation::doRead()
+{
+ MegaAddr addr;
+ socklen_t addrlen = sizeof(addr);
+
+ // reset errno:
+ m_errno= 0;
+
+ // reset the mark:
+ resetReadMark();
+
+ int fd = ::accept( readFd(), &addr.m_addr, &addrlen);
+ if (fd < 0)
+ {
+ // handle errors.
+ m_errno= errno;
+ switch (m_errno)
+ {
+ case EBADF:
+ case EINVAL:
+ case ENOTSOCK:
+ // should not happen...
+ close();
+ break;
+ default:;
+ }
+ return;
+ }
+
+ if (addrlen < sizeof(addr))
+ {
+ // in case of unix domain socket: terminate the path!
+ // NOTE we are doing this here since we don't pass the length info.
+ addr.m_buffer[addrlen]= 0;
+ }
+ else
+ {
+ //something went terribly wrong!!
+ // the resulting address structure is larger than it ever could be...
+ ::close(fd);
+ return;
+ }
+
+ IOImplementationPtr connection= acceptNewConnection(fd, &addr.m_addr);
+ if(!connection)
+ {
+ ::close(fd);
+ return;
+ }
+ if (m_new_connection_base_callback)
+ {
+ m_new_connection_base_callback(connection);
+ }
+} // eo ServerSocketBaseImplementation::doRead()
+
+
+/**
+ * @brief handles write events.
+ *
+ * since server sockets never ever get a write mark, something must
+ * be wrong and the connection is closed!
+ */
+void ServerSocketBaseImplementation::doWrite()
+{
+ // if this method is called, something went wrong...
+ close();
+ //TODO throw something?!
+} // eo ServerSocketBaseImplementation::doWrite()
+
+
+
+/**
+ * @brief sets a function which is called when a new connection was established.
+ *
+ * The function gets a (shared) pointer to the new connetion as parameter and is
+ * expected to store it when it accepts the connection.
+ * (Else the conenction gets deleted after the function was called.)
+ *
+ * @param func the function which hsould be called on new conenctions.
+ */
+void ServerSocketBaseImplementation::setNewConnectionBaseCallback(
+ const NewConnectionBaseCallbackFunc& func)
+{
+ m_new_connection_base_callback= func;
+} // eo ServerSocketBaseImplementation::setNewConnectionBaseCallback(NewConnectionBaseCallbackFunc&)
+
+
+
+/**
+ * @brief callback for new connections.
+ *
+ * The base method always returns an empty pointer.
+ *
+ * Derived classes must override this method and do something useful with
+ * the passed file descriptor.
+ *
+ * @param fd the file descriptor of the new connection.
+ * @param addr pointer to the address structure filled from ::accept()
+ * @return shared pointer to the new IO class instance (; empty if not accepted)
+ */
+IOImplementationPtr ServerSocketBaseImplementation::acceptNewConnection(
+ int fd, boost::any addr)
+{
+ // needs to be defined in derived class!
+ return IOImplementationPtr();
+} // eo ServerSocketBaseImplementation::acceptNewConnection(int,boost::any)
+
+
+
+/*
+** implementation of UnixIOSocket
+*/
+
+
+UnixIOSocket::UnixIOSocket()
+{
+} // eo UnixIOSocket::UnixIOSocket()
+
+
+
+UnixIOSocket::UnixIOSocket(const std::string& path)
+: m_peer_pid(0)
+, m_peer_uid(0)
+, m_peer_gid(0)
+{
+ open(path);
+} // eo UnixIOSocket::UnixIOSocket(const std::string&)
+
+
+UnixIOSocket::UnixIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid, unsigned int peer_uid, unsigned int peer_gid)
+: IOImplementation(fd,fd)
+, m_path(path)
+, m_peer_pid(peer_pid)
+, m_peer_uid(peer_uid)
+, m_peer_gid(peer_gid)
+{
+} // eo UnixIOSocket::UnixIOSocket(int,const std::string&,unsigned,unsigned,unsigned)
+
+
+/**
+ * @brief opens a (client) connection to an unix domain socket.
+ *
+ * @param path the path the server is listening on.
+ * @return @a true iff the connection was successfully opened.
+ */
+bool UnixIOSocket::open(const std::string& path)
+{
+ if (opened()) close();
+
+ m_errno= 0;
+ m_path.clear();
+
+ if (path.empty() || path.size() >= PATH_MAX)
+ {
+ return false;
+ }
+
+ int fd= ::socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd<0)
+ {
+ m_errno= errno;
+ return false;
+ }
+
+ {
+ MegaAddr addr;
+ addr.m_addr_un.sun_family= AF_UNIX;
+ strncpy(addr.m_addr_un.sun_path, path.c_str(), PATH_MAX);
+ if (::connect(fd,(sockaddr*)&addr.m_addr_un, SUN_LEN(&addr.m_addr_un)) < 0)
+ {
+ m_errno= errno;
+ ::close(fd);
+ return false;
+ }
+ }
+ m_path= path;
+ setReadFd(fd);
+ setWriteFd(fd);
+ return true;
+} // eo UnixIOSocket::open(const std::string&,int)
+
+
+/*
+** implementation of UnixServerSocketBase
+*/
+
+
+UnixServerSocketBase::UnixServerSocketBase()
+{
+} // eo UnixServerSocketBase::UnixServerSocketBase
+
+
+UnixServerSocketBase::UnixServerSocketBase(const std::string& path, int mode)
+{
+ open(path,mode);
+} // eo UnixServerSocketBase::UnixServerSocketBase(const std::string&,int)
+
+
+/**
+ * @brief opens the server part of an unix domain socket.
+ *
+ * @param path the path the new port should listen on.
+ * @param mode the mode for the path.
+ * @return @a true iff the port was successfully opened.
+ */
+bool UnixServerSocketBase::open(const std::string& path, int mode)
+{
+ if (opened()) close();
+ m_errno= 0;
+ if (path.empty() || path.size() >= PATH_MAX)
+ {
+ return false;
+ }
+
+ int fd= ::socket(PF_UNIX, SOCK_STREAM, 0);
+ if (fd<0)
+ {
+ m_errno= errno;
+ return false;
+ }
+
+ {
+ MegaAddr addr;
+ addr.m_addr_un.sun_family= AF_UNIX;
+ strncpy(addr.m_addr_un.sun_path, path.c_str(), PATH_MAX);
+ ::I2n::unlink(path); // just in case...
+ mode_t old_mask= ::umask( (mode & 0777) ^ 0777);
+ if (::bind(fd,(sockaddr*)&addr.m_addr_un, SUN_LEN(&addr.m_addr_un)) < 0)
+ {
+ m_errno= errno;
+ ::umask(old_mask);
+ ::close(fd);
+ return false;
+ }
+ ::umask(old_mask);
+ }
+ m_path= path;
+
+ {
+ int res= ::listen(fd,8);
+ if (res < 0)
+ {
+ m_errno= errno;
+ ::close(fd);
+ return false;
+ }
+ }
+ setReadFd(fd);
+ return true;
+} // eo UnixServerSocketBase::open(const std::string&,int)
+
+
+/**
+ * @brief called from base class to create a new connection instance.
+ *
+ * This method accepts only connections to unix domain sockets.
+ * It also tries to determine the peer pid, uid and gid.
+ *
+ * @param fd the file descriptor of a freshly accepted connection.
+ * @param addr conatins "pointer to struct sockaddr"
+ * @return @a a (shared) pointer to the new connection isntance; empty if none was
+ * created.
+ */
+IOImplementationPtr UnixServerSocketBase::acceptNewConnection(int fd, boost::any addr)
+{
+ struct sockaddr *addr_ptr= NULL;
+ try {
+ addr_ptr = boost::any_cast<struct sockaddr*>(addr);
+ }
+ catch (boost::bad_any_cast&)
+ {
+ return IOImplementationPtr();
+ }
+ // check for the right family:
+ if (addr_ptr->sa_family != AF_UNIX)
+ {
+ return IOImplementationPtr();
+ }
+ struct sockaddr_un *un_ptr = reinterpret_cast<struct sockaddr_un*>(addr_ptr);
+ std::string peer_path( un_ptr->sun_path );
+ unsigned peer_pid=0;
+ unsigned peer_gid=0;
+ unsigned peer_uid=0;
+#ifdef __linux__
+ { // the linux way to get peer info (pid,gid,uid):
+ struct ucred cred;
+ socklen_t cred_len = sizeof(cred);
+ if (getsockopt(fd,SOL_SOCKET,SO_PEERCRED,&cred,&cred_len) == 0)
+ {
+ peer_pid= cred.pid;
+ peer_uid= cred.uid;
+ peer_gid= cred.gid;
+ }
+ }
+#else
+#error dont know how to determine peer info.
+#endif
+
+ UnixIOSocketPtr ptr( createIOSocket(fd, peer_path, peer_pid, peer_uid, peer_gid) );
+ return ptr;
+} // eo UnixServerSocketBase::acceptNewConnection(int,boost::any);
+
+
+/**
+ * @brief "real" creator of the connection instance.
+ *
+ * called by UnixServerSocketBase::acceptNewConnection to create the new io instance.
+ *
+ * @param fd file descriptor for the socket
+ * @param path path as delivered by peer.
+ * @param peer_pid peer pid.
+ * @param peer_uid peer uid.
+ * @param peer_gid peer gid.
+ * @return (shared) pointer to the new io instance.
+ */
+UnixIOSocketPtr UnixServerSocketBase::createIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid,
+ unsigned int peer_uid, unsigned int peer_gid)
+{
+ return UnixIOSocketPtr(
+ new UnixIOSocket(fd, path, peer_pid, peer_uid, peer_gid)
+ );
+} // eo UnixServerSocketBase::createIOSocket(int,const std::string&,unsigned,unsigned,unsigned)
+
+
+
+}// eo namespace SimpleIo
+}// eo namespace I2n
--- /dev/null
+/** @file
+ * @brief socket classes for the SimpleIo framework.
+ *
+ *
+ * (c) Copyright 2008 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+#ifndef __SIMPLEIO__SIMPLESOCKET_HPP__
+#define __SIMPLEIO__SIMPLESOCKET_HPP__
+
+#include "simpleio.hpp"
+
+#include <string>
+#include <boost/any.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/type_traits/is_base_of.hpp>
+#include <boost/static_assert.hpp>
+#include <boost/function.hpp>
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+typedef boost::shared_ptr< IOImplementation > IOImplementationPtr;
+
+
+/**
+ * @brief base class for server sockets.
+ *
+ * Contains all the stuff which is common for different types of server sockets.
+ */
+class ServerSocketBaseImplementation
+: public IOImplementation
+{
+ public:
+ typedef boost::function< void(IOImplementationPtr) > NewConnectionBaseCallbackFunc;
+
+ public:
+
+ void setNewConnectionBaseCallback( const NewConnectionBaseCallbackFunc& func);
+
+ protected:
+ ServerSocketBaseImplementation();
+
+
+ virtual void doRead();
+ virtual void doWrite();
+
+ virtual IOImplementationPtr acceptNewConnection(int fd, boost::any addr);
+
+
+ protected:
+
+ NewConnectionBaseCallbackFunc m_new_connection_base_callback;
+
+}; // eo class ServerSocketBaseImplementation
+
+
+typedef boost::shared_ptr< ServerSocketBaseImplementation > ServerSocketBaseImplementationPtr;
+
+
+/*
+** unix domain sockets
+*/
+
+
+template<
+ class IOClass
+>
+class UnixServerSocket;
+
+
+/**
+ * @brief spezialized IO class for unix domain sockets.
+ *
+ */
+class UnixIOSocket
+: public IOImplementation
+{
+ public:
+ UnixIOSocket();
+ UnixIOSocket(const std::string& path);
+
+ bool open(const std::string& path);
+
+ protected:
+ friend class UnixServerSocketBase;
+ friend class UnixServerSocket<UnixIOSocket>;
+
+ UnixIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid, unsigned int peer_uid, unsigned int peer_gid);
+
+ protected:
+
+ std::string m_path;
+ unsigned int m_peer_pid;
+ unsigned int m_peer_uid;
+ unsigned int m_peer_gid;
+
+}; // eo class UnixIOSocket
+
+typedef boost::shared_ptr< UnixIOSocket > UnixIOSocketPtr;
+
+
+
+/**
+ * @brief spezialized server socket class for unix domain sockets.
+ *
+ */
+class UnixServerSocketBase
+: public ServerSocketBaseImplementation
+{
+ public:
+ UnixServerSocketBase();
+ UnixServerSocketBase(const std::string& path, int mode=0600);
+
+ bool open(const std::string& path, int mode= 0600);
+
+ protected:
+
+ virtual IOImplementationPtr acceptNewConnection(int fd, boost::any addr);
+
+ virtual UnixIOSocketPtr createIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid,
+ unsigned int peer_uid, unsigned int peer_gid);
+
+ protected:
+
+ std::string m_path;
+
+}; // eo class UnixServerSocketBase
+
+
+/**
+ * @brief unix server socket class which "produces" connections of a determined type.
+ *
+ + @param IOClass the type of the connections.
+ */
+template<
+ class IOClass
+>
+class UnixServerSocket
+: public UnixServerSocketBase
+{
+ BOOST_STATIC_ASSERT(( boost::is_base_of<UnixIOSocket,IOClass>::value ));
+
+ public:
+ typedef boost::shared_ptr< IOClass > IOClassPtr;
+ typedef boost::function< void(IOClassPtr) > NewConnectionCallbackFunc;
+
+ public:
+
+ UnixServerSocket()
+ : UnixServerSocketBase()
+ {}
+
+ UnixServerSocket(const std::string& path, int mode=0600)
+ : UnixServerSocketBase(path,mode)
+ {}
+
+ void setNewConnectionCallback( const NewConnectionCallbackFunc& func)
+ {
+ if (func)
+ {
+ UnixServerSocketBase::setNewConnectionBaseCallback(
+ boost::bind(
+ func,
+ boost::bind<IOClassPtr, IOImplementationPtr>(
+ &UnixServerSocket::my_ptr_cast,
+ _1
+ )
+ )
+ );
+ }
+ else
+ {
+ UnixServerSocketBase::setNewConnectionBaseCallback(
+ NewConnectionBaseCallbackFunc()
+ );
+ }
+ }
+
+ protected:
+
+ virtual UnixIOSocketPtr createIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid,
+ unsigned int peer_uid, unsigned int peer_gid)
+ {
+ return UnixIOSocketPtr(
+ new IOClass(fd, path, peer_pid, peer_uid, peer_gid)
+ );
+ }
+
+ static IOClassPtr my_ptr_cast(IOImplementationPtr ptr)
+ {
+ return boost::dynamic_pointer_cast<IOClass>(ptr);
+ }
+
+}; // eo class UnixServerSocket
+
+
+}// eo namespace SimpleIo
+}// eo namespace I2n
+
+
+#endif
--- /dev/null
+/** @file
+ *
+ * (c) Copyright 2007 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+//#define NOISEDEBUG
+
+
+#include "simpletimer.hpp"
+
+
+#ifdef NOISEDEBUG
+#include <iostream>
+#include <iomanip>
+#define DOUT(msg) std::cout << msg << std::endl
+#define FODOUT(obj,msg) std::cout << typeid(*obj).name() << "[" << obj << "]:" << msg << std::endl
+#define ODOUT(msg) std::cout << typeid(*this).name() << "[" << this << "]:" << msg << std::endl
+#else
+#define DOUT(msg) do {} while (0)
+#define FODOUT(obj,msg) do {} while (0)
+#define ODOUT(msg) do {} while (0)
+#endif
+
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+/*
+ * Implementation of SimpleTimer
+ */
+
+SimpleTimer::SimpleTimer()
+{
+} // eo SimpleTimer::SimpleTimer()
+
+
+SimpleTimer::SimpleTimer(const MilliTime& delta, const TimerSignal::slot_function_type& action)
+{
+ addAction(action);
+ startTimer(delta);
+} // eo SimpleTimer::SimpleTimer(const MilliTime&, const TimerSignal::slot_type&)
+
+
+SimpleTimer::SimpleTimer(long milli_seonds_delta, const TimerSignal::slot_function_type& action)
+{
+ addAction(action);
+ startTimerMS(milli_seonds_delta);
+} // eo SimpleTimer::SimpleTimer(long, const TimerSignal::slot_type&)
+
+
+void SimpleTimer::execute()
+{
+ ODOUT("execute()!");
+ m_timer_signal();
+ m_timer_signal_p(this);
+} // eo SimpleTimer::execute
+
+
+void SimpleTimer::addAction( const TimerSignal::slot_function_type& action )
+{
+ m_timer_signal.connect(action);
+} // eo SimpleTimer::addAction(const TimerSignal::slot_type&)
+
+
+void SimpleTimer::addActionP( const TimerSignalP::slot_function_type& action )
+{
+ m_timer_signal_p.connect(action);
+} // eo SimpleTimer::addAction(const TimerSignalP::slot_type&)
+
+
+void SimpleTimer::startTimer( const MilliTime& delta )
+{
+ m_delta= delta;
+ setDeltaWhenTime( delta );
+ activate(true);
+#ifdef NOISEDEBUG
+ MilliTime now, t;
+ get_current_time(now);
+ t= getWhenTime();
+ MilliTime dt= t-now;
+ ODOUT("startTimer");
+ ODOUT(" now: sec="<< now.mt_sec << ", msec="<<now.mt_msec);
+ ODOUT(" t: sec="<< t.mt_sec << ", msec="<<t.mt_msec);
+ ODOUT(" dt: sec="<< dt.mt_sec << ", msec="<<dt.mt_msec);
+#endif
+} // eo SimpleTimer::startTimer(const MilliTime&)
+
+
+void SimpleTimer::startTimerMS( long milli_seconds )
+{
+ startTimer( MilliTime(0,milli_seconds) );
+} // eo SimpleTimer::stratTimerMS(long)
+
+
+void SimpleTimer::stopTimer()
+{
+ deactivate();
+} // eo SimpleTimer::stopTimer
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
--- /dev/null
+/** @file
+ *
+ * provides timer objects based on the TimerBase class
+ *
+ * (c) Copyright 2007 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+#ifndef _SIMPLEIO_SIMPLETIMER_HPP_
+#define _SIMPLEIO_SIMPLETIMER_HPP_
+
+#include "simpleio.hpp"
+
+#include <boost/signal.hpp>
+
+namespace I2n
+{
+namespace SimpleIo
+{
+
+
+class SimpleTimer : public TimerBase
+{
+ public:
+
+ typedef boost::signal<void()> TimerSignal;
+ typedef boost::signal<void(SimpleTimer*)> TimerSignalP;
+
+ public:
+
+ SimpleTimer();
+
+ // convenience constructors for simple timeouts:
+ SimpleTimer(const MilliTime& delta, const TimerSignal::slot_function_type& action);
+ SimpleTimer(long milli_seonds_delta, const TimerSignal::slot_function_type& action);
+
+ // add actions:
+ void addAction( const TimerSignal::slot_function_type& action );
+ void addActionP( const TimerSignalP::slot_function_type& action );
+
+ // start the timer:
+ void startTimer( const MilliTime& delta );
+ void startTimerMS( long milli_seconds );
+
+ // stop the timer:
+ void stopTimer();
+
+ protected:
+ MilliTime m_delta;
+
+ TimerSignal m_timer_signal;
+ TimerSignalP m_timer_signal_p;
+
+ virtual void execute();
+}; // eo class SimpleTimer
+
+
+} // eo namespace SimpleIo
+} // eo namespace I2n
+
+#endif
--- /dev/null
+/**
+ * @file
+ * @brief
+ *
+ * @author Reinhard Pfau \<reinhard.pfau@intra2net.com\>
+ *
+ * @copyright © Copyright 2008 Intra2Net AG
+ * @license commercial
+ * @contact info@intra2net.com
+ *
+ */
--- /dev/null
+/**
+ * @file
+ * @brief
+ *
+ * @author Reinhard Pfau \<reinhard.pfau@intra2net.com\>
+ *
+ * @copyright © Copyright 2008 Intra2Net AG
+ * @license commercial
+ * @contact info@intra2net.com
+ *
+ */
--- /dev/null
+INCLUDES = -I$(top_srcdir)/. -I$(top_srcdir)/common -I$(top_srcdir)/connd \
+ -I$(top_srcdir)/simpleio @BOOST_CPPFLAGS@ @CPPUNIT_CFLAGS@ @LIBI2NCOMMON_CFLAGS@
+METASOURCES = AUTO
+check_PROGRAMS = testsimpleio
+testsimpleio_SOURCES = test.cpp test_simpleio_basics.cpp
+testsimpleio_LDADD = $(top_builddir)/simpleio/libsimpleio.la @BOOST_LDFLAGS@ \
+ @BOOST_SIGNALS_LIB@ @CPPUNIT_LIBS@ @LIBI2NCOMMON_LIBS@
+
+TESTS = testsimpleio
--- /dev/null
+/***************************************************************************
+ * Copyright (C) 2004 by Intra2net AG *
+ * info@intra2net.com *
+ * *
+ ***************************************************************************/
+
+#include <iostream>
+#include <iomanip>
+#include <string>
+
+#include <time.h>
+#include <sys/timeb.h>
+
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/TestListener.h>
+#include <cppunit/TestFailure.h>
+#include <cppunit/TestResult.h>
+#include <cppunit/CompilerOutputter.h>
+
+class VerboseTimingListener : public CppUnit::TestListener
+{
+ private:
+ double start_time;
+ std::string resultstr;
+
+ double get_time(void)
+ {
+ struct timeb tb;
+ ftime(&tb);
+ return tb.time+(static_cast<float>(tb.millitm)/1000);
+ }
+
+ public:
+
+ void startTest( CppUnit::Test *test )
+ {
+ resultstr="OK";
+ std::cout << test->getName() << ": ";
+ start_time=get_time();
+ }
+
+ void endTest( CppUnit::Test *test )
+ {
+ double timediff=get_time()-start_time;
+
+ // fix clock unpreciseness for small timespans
+ if (timediff < 0) timediff=0;
+
+ std::cout << resultstr << " ("
+ << std::fixed << std::setprecision(3)
+ << timediff << " sec)" << std::endl;
+ }
+
+ void addFailure(const CppUnit::TestFailure &failure)
+ {
+ if(failure.isError())
+ resultstr="ERROR";
+ else
+ resultstr="FAIL";
+ }
+};
+
+int main(int argc, char **argv)
+{
+ CppUnit::TextTestRunner runner;
+ CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry();
+
+ // set output format that KDevelop can catch errors
+ CppUnit::CompilerOutputter *op=CppUnit::CompilerOutputter::defaultOutputter(&runner.result(),std::cout);
+ op->setLocationFormat("%p:%l: error: ");
+ runner.setOutputter(op);
+
+ // show every test with timing
+ VerboseTimingListener listener;
+ runner.eventManager().addListener(&listener);
+
+ runner.addTest(registry.makeTest());
+
+ // run all tests in registry (not using the default progress listener)
+ bool wasSucessful = runner.run("",false,true,false);
+
+ return (wasSucessful ? 0 : 1);
+}
--- /dev/null
+/** @file
+ *
+ * (c) Copyright 2007 by Intra2net AG
+ *
+ * info@intra2net.com
+ */
+
+//#define NOISEDEBUG
+
+#include <string>
+#include <iostream>
+#include <iomanip>
+#include <vector>
+
+#include <cppunit/extensions/TestFactoryRegistry.h>
+#include <cppunit/ui/text/TestRunner.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <simpleio.hpp>
+#include <simplepipe.hpp>
+#include <simpleprocess.hpp>
+#include <simpletimer.hpp>
+#include <simplecallout.hpp>
+#include <simplesocket.hpp>
+#include <filefunc.hxx>
+#include <containerfunc.hpp>
+#include <boost/signal.hpp>
+#include <boost/bind.hpp>
+#include <boost/random.hpp>
+
+
+#ifdef NOISEDEBUG
+#define DOUT(msg) std::cout << msg << std::endl
+#else
+#define DOUT(msg) do {} while (0)
+#endif
+
+
+using namespace I2n;
+using namespace I2n::SimpleIo;
+
+
+using namespace CppUnit;
+
+namespace {
+
+
+struct Counter
+{
+ int value;
+
+
+ Counter() : value(0) { DOUT("Counter construct");}
+
+ void reset() { value=0;}
+
+ void advance()
+ {
+ DOUT(" advance called");
+ ++value;
+ }
+
+ int operator()()
+ {
+ DOUT(" () called");
+ return ++value;
+ }
+}; // eo struct Counter
+
+
+class TestTimer : public TimerBase
+{
+ public:
+
+ TestTimer()
+ : m_counter(0u)
+ {
+ } // eo TestTimer()
+
+
+ void setDelta(long msec)
+ {
+ setDeltaWhenTime(0,msec);
+ activate();
+ } // eo setDelta(long)
+
+
+ unsigned m_counter;
+
+ protected:
+
+ virtual void execute()
+ {
+ ++m_counter;
+ } // eo execute()
+
+}; // eo class TestTimer
+
+
+
+struct ReceivedData
+{
+ std::vector<std::string> m_received_vector;
+ std::string m_received_string;
+
+ unsigned long m_count_lines;
+ unsigned long m_data_size;
+
+
+ void resetReceivedData()
+ {
+ m_received_vector.clear();
+ m_received_string.clear();
+ m_count_lines= 0uL;
+ m_data_size= 0uL;
+ } // eo reset
+
+
+ void receiveData(const std::string& data)
+ {
+ m_received_vector.push_back(data);
+ m_received_string.append(data);
+ ++m_count_lines;
+ m_data_size += data.size();
+ } // eo receiveData(const std::string&)
+
+}; // eo struct ReceivedData
+
+
+
+class TestPipe : public SimplePipe, public ReceivedData
+{
+ public:
+ TestPipe()
+ : SimplePipe()
+ {
+ signal_received_string.connect( boost::bind(&TestPipe::receiveData, this, _1) );
+ } // eo TestPipe()
+
+
+
+ protected:
+
+}; // eo class TestPipe
+
+
+class TestIO : public SimpleIO2, public ReceivedData
+{
+ public:
+ TestIO()
+ {
+ signal_received_string.connect( boost::bind(&TestIO::receiveData, this, _1) );
+ }
+
+}; // eo TestIO
+
+
+class TestProcess : public ProcessImplementation, public ReceivedData
+{
+ public:
+ TestProcess(
+ const std::string& path,
+ const std::vector<std::string>& args= std::vector<std::string>() )
+ : ProcessImplementation(path,args)
+ {
+ m_signal_read.connect( boost::bind(&TestProcess::slotReceivedData, this) );
+ } // eo Testprocess(const std::string&, const std::vector<std::string>&)
+
+ TestProcess(
+ const std::string& path,
+ const std::string& arg1 )
+ : ProcessImplementation(
+ path,
+ TransientPushBackFiller< std::string,std::vector >()(arg1)
+ )
+ {
+ m_signal_read.connect( boost::bind(&TestProcess::slotReceivedData, this) );
+ } // eo Testprocess(const std::string&, const std::vector<std::string>&)
+
+ TestProcess(
+ const std::string& path,
+ const std::string& arg1, const std::string& arg2 )
+ : ProcessImplementation(
+ path,
+ TransientPushBackFiller< std::string, std::vector >()(arg1)(arg2)
+ )
+ {
+ m_signal_read.connect( boost::bind(&TestProcess::slotReceivedData, this) );
+ } // eo Testprocess(const std::string&, const std::vector<std::string>&)
+
+
+
+ pid_t pid() { return m_pid; }
+
+ bool kill( Signal signal) { return ProcessImplementation::kill(signal); }
+
+ protected:
+
+ void slotReceivedData()
+ {
+ receiveData(m_input_buffer);
+ m_input_buffer.clear();
+ } // eo slotReceivedData()
+
+}; // eo class TestProcess
+
+
+
+class TestUnixIOSocket
+: public UnixIOSocket
+, public ReceivedData
+{
+ public:
+
+ TestUnixIOSocket()
+ : UnixIOSocket()
+ {
+ m_signal_read.connect( boost::bind(&TestUnixIOSocket::slotReceivedData, this) );
+ } // eo TestUnixIOSocket()
+
+
+ TestUnixIOSocket(
+ int fd, const std::string& path,
+ unsigned int peer_pid, unsigned int peer_uid, unsigned int peer_gid
+ )
+ : UnixIOSocket(fd, path, peer_pid, peer_uid, peer_gid)
+ {
+ m_signal_read.connect( boost::bind(&TestUnixIOSocket::slotReceivedData, this) );
+ } // eo TestUnixIOSocket()
+
+
+ void sendData(const std::string& data)
+ {
+ lowSend(data);
+ } // eo sendData(const std::string&)
+
+ protected:
+
+ void slotReceivedData()
+ {
+ receiveData(m_input_buffer);
+ m_input_buffer.clear();
+ } // eo slotReceivedData()
+
+}; // eo class TestUnixIOSocket
+
+typedef boost::shared_ptr< TestUnixIOSocket > TestUnixIOSocketPtr;
+
+
+class UnixIOSocketHolder
+: public std::vector< UnixIOSocketPtr >
+{
+ public:
+
+ void operator()(UnixIOSocketPtr ptr)
+ {
+ push_back(ptr);
+ }
+
+ void storeBase (IOImplementationPtr ptr)
+ {
+ push_back(boost::dynamic_pointer_cast< UnixIOSocket >(ptr) );
+ }
+
+ void store (UnixIOSocketPtr ptr)
+ {
+ push_back(ptr);
+ }
+
+ TestUnixIOSocketPtr get(int idx)
+ {
+ return boost::dynamic_pointer_cast<
+ TestUnixIOSocket
+ >( (*this)[idx] );
+ }
+}; // eo class UnixIOSocketHolder
+
+
+/// global random generator (from boost lib).
+boost::mt19937 g_random_gen;
+
+
+/**
+ * generates a string with random characters from a given ASCII subset.
+ * @param len the desired length of the output string
+ * @return a random string of length @a len
+ */
+std::string makeRandomAsciiString(std::string::size_type len)
+{
+ static std::string chars("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz=+-*/%(){}<>.,:;\\");
+ std::string s;
+
+ boost::uniform_int<> discreter(0, chars.size()-1);
+ boost::variate_generator<boost::mt19937&, boost::uniform_int<> > idxgen(g_random_gen, discreter);
+
+ for(; len-->0;) s+= chars.at( idxgen() );
+
+ return s;
+} // eo makeRandomAsciiString
+
+
+} // eo namespace <anonymous>
+
+
+
+class TestSimpleioBasics : public TestFixture
+{
+ CPPUNIT_TEST_SUITE(TestSimpleioBasics);
+
+ CPPUNIT_TEST(EmptyBackendStepCall);
+ CPPUNIT_TEST(NonEmptyBackendStepCall);
+ CPPUNIT_TEST(SingleTimerShot);
+ CPPUNIT_TEST(SimpleTimerShot);
+ CPPUNIT_TEST(SimpleTimerShot2);
+
+ CPPUNIT_TEST(EmptyWantTest);
+ CPPUNIT_TEST(SimplePipeTest);
+ CPPUNIT_TEST(SimplePipePump);
+
+ CPPUNIT_TEST(SimpleProcessTestBinTrue);
+ CPPUNIT_TEST(SimpleProcessTestBinFalse);
+ CPPUNIT_TEST(SimpleProcessTestEcho);
+ CPPUNIT_TEST(SimpleProcessTestStderr);
+ CPPUNIT_TEST(SignaledProcessTermination);
+
+
+ CPPUNIT_TEST(CallOut1);
+ CPPUNIT_TEST(CallOut2);
+ CPPUNIT_TEST(RemoveCallOut1);
+
+ CPPUNIT_TEST(FrozenCall_Thaw);
+ CPPUNIT_TEST(FrozenCall_Decay);
+
+
+ CPPUNIT_TEST(UnixSockets_ClientServer);
+
+
+ //CPPUNIT_TEST(Dummy);
+ CPPUNIT_TEST_SUITE_END();
+
+ protected:
+
+ Backend *backend;
+ std::set<std::string> used_check_files;
+
+
+ template<class Callable>
+ bool backendLoopUntil( Callable condition, int maxLoops=100 )
+ {
+ for (int i=std::max(maxLoops,1); i-->0 && ! condition();)
+ {
+ backend->doOneStep(10);
+ }
+ return condition();
+ } // eo backendLoopUntil
+
+
+ bool backendStep(int msTimeout= 10, int count=1)
+ {
+ bool res= true;
+ for(;count-->0 && res;)
+ {
+ res= backend->doOneStep(msTimeout);
+ }
+ return res;
+ } // eo backendStep
+
+
+ std::string getCheckFilepath(std::string tag)
+ {
+ std::string result;
+ result= "__unittest__" + tag + ".dat";
+ used_check_files.insert(result);
+ return result;
+ } // eo get_check_file_path
+
+
+ void removeCheckFiles()
+ {
+ for(std::set<std::string>::iterator it= used_check_files.begin();
+ it != used_check_files.end();
+ ++it)
+ {
+ std::string filepath(*it);
+ if (path_exists(filepath))
+ {
+ unlink(filepath);
+ }
+ }
+ used_check_files.clear();
+ } // eo removeCheckFiles
+
+
+
+ public:
+
+ void setUp()
+ {
+ backend = Backend::getBackend();
+ installChildHandler();
+ used_check_files.clear();
+ } // eo setUp
+
+
+ void tearDown()
+ {
+ restoreChildHandler();
+ removeCheckFiles();
+ } // eo tearDown
+
+
+ /*
+ * the tests:
+ */
+
+
+ /*
+ ** basics:
+ */
+
+
+ void EmptyBackendStepCall()
+ {
+ CPPUNIT_ASSERT( backend );
+
+ // a backend call without active objects should return false:
+ bool result = backend->doOneStep(0);
+
+ CPPUNIT_ASSERT_EQUAL( false, result );
+ } // eo EmptyBackendStepCall
+
+
+
+ void NonEmptyBackendStepCall()
+ {
+ CPPUNIT_ASSERT(backend);
+
+ {
+ TestTimer timer;
+ timer.setDelta(10);
+ // with an active object, a step should return true:
+ bool result = backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL( true, result );
+ // the timer should not be executed:
+ CPPUNIT_ASSERT_EQUAL( 0u, timer.m_counter );
+ }
+ // now it should return false:
+ bool result = backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL( false, result );
+ } // eo NonEmptyBackendStepCall
+
+
+
+ /**
+ * check for timer to execute immediatly.
+ */
+ void SingleTimerShot()
+ {
+ CPPUNIT_ASSERT(backend);
+
+ TestTimer timer;
+ timer.setDelta(0); // shot now!
+
+ bool result = backend->doOneStep(10);
+
+ CPPUNIT_ASSERT_EQUAL( true, result );
+ // the timer should be executed once:
+ CPPUNIT_ASSERT_EQUAL( 1u, timer.m_counter );
+
+ result = backend->doOneStep(0);
+
+ CPPUNIT_ASSERT_EQUAL( false, result );
+ // the timer should not be executed again:
+ CPPUNIT_ASSERT_EQUAL( 1u, timer.m_counter );
+
+ } // eo SingleTimerShot()
+
+
+
+ /**
+ * tests a simple timer class to be executed with a timeout.
+ */
+ void SimpleTimerShot()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ SimpleTimer timer1;
+ Counter counter1;
+ timer1.addAction( boost::bind(&Counter::advance,&counter1) );
+ CPPUNIT_ASSERT_EQUAL(false, timer1.active());
+
+ timer1.startTimerMS( 100 );
+ CPPUNIT_ASSERT_EQUAL(true, timer1.active());
+
+ res=backend->doOneStep( 1000 );
+ CPPUNIT_ASSERT_EQUAL( true, res );
+
+ CPPUNIT_ASSERT_EQUAL( 1, counter1.value );
+ } // eo SimpleTimerShot
+
+
+
+ /**
+ * tests 3 timers; after the first was active, disable another and check if the remaining one fires.
+ */
+ void SimpleTimerShot2()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ SimpleTimer timer1, timer2, timer3;
+ Counter counter1, counter2, counter3;
+ timer1.addAction( boost::bind(&Counter::advance,&counter1) );
+ timer2.addAction( boost::bind(&Counter::advance,&counter2) );
+ timer3.addAction( boost::bind(&Counter::advance,&counter3) );
+ CPPUNIT_ASSERT_EQUAL(false, timer1.active());
+ CPPUNIT_ASSERT_EQUAL(false, timer2.active());
+ CPPUNIT_ASSERT_EQUAL(false, timer3.active());
+
+ timer1.startTimerMS( 100 );
+ timer2.startTimerMS( 500 );
+ timer3.startTimerMS( 400 );
+ CPPUNIT_ASSERT_EQUAL(true, timer1.active());
+ CPPUNIT_ASSERT_EQUAL(true, timer2.active());
+ CPPUNIT_ASSERT_EQUAL(true, timer3.active());
+
+ res=backend->doOneStep( 1000 );
+ CPPUNIT_ASSERT_EQUAL( true, res );
+
+ CPPUNIT_ASSERT_EQUAL(false, timer1.active());
+ CPPUNIT_ASSERT_EQUAL(true, timer2.active());
+ CPPUNIT_ASSERT_EQUAL(true, timer3.active());
+
+ CPPUNIT_ASSERT_EQUAL( 1, counter1.value );
+ CPPUNIT_ASSERT_EQUAL( 0, counter2.value );
+ CPPUNIT_ASSERT_EQUAL( 0, counter3.value );
+
+ // now stop the next timer:
+ timer3.stopTimer();
+ CPPUNIT_ASSERT_EQUAL(false, timer3.active());
+
+ res=backend->doOneStep( 1000 );
+ CPPUNIT_ASSERT_EQUAL( true, res );
+
+ CPPUNIT_ASSERT_EQUAL( 1, counter1.value );
+ CPPUNIT_ASSERT_EQUAL( 1, counter2.value );
+ CPPUNIT_ASSERT_EQUAL( 0, counter3.value );
+ } // eo SimpleTimerShot2
+
+
+
+
+ /*
+ ** I/O tests:
+ */
+
+ void EmptyWantTest()
+ {
+ IOImplementation io;
+
+ CPPUNIT_ASSERT_EQUAL(false, io.wantRead() );
+ CPPUNIT_ASSERT_EQUAL(false, io.wantWrite() );
+ } // eo EmptyWantTest
+
+
+ /**
+ * a simple pipe (and io) test.
+ *
+ * This test basically tests the io framework.
+ * It opens two connected pipes and sends a test string in each direction.
+ * It tests the following functionalities of the base classes:
+ * - set write marks in backend step (enabling direct send of data)
+ * - low send data
+ * - construct and interpret poll() data structures
+ * - receive data
+ * - signal chains for received data
+ * - eof detection
+ * .
+ *
+ */
+ void SimplePipeTest()
+ {
+ static const std::string test_string("a test string");
+ static const std::string test_string2("only another short test string");
+
+ CPPUNIT_ASSERT(backend);
+
+ TestPipe pipe1, pipe2;
+
+ bool res= pipe1.makePipe(pipe2);
+
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ CPPUNIT_ASSERT_EQUAL(true, pipe1.opened());
+ CPPUNIT_ASSERT_EQUAL(true, pipe2.opened());
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ pipe1.sendString(test_string);
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ CPPUNIT_ASSERT_EQUAL( test_string, pipe2.m_received_string );
+
+ pipe2.sendString(test_string2);
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ CPPUNIT_ASSERT_EQUAL( test_string2, pipe1.m_received_string );
+
+ pipe1.close();
+ CPPUNIT_ASSERT_EQUAL(false, pipe1.opened());
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ CPPUNIT_ASSERT_EQUAL(true, pipe2.eof());
+ } // eo SimplePipeTest
+
+
+
+ /**
+ * sends a larger data chunk through a pipe.
+ * This tests if sending and receiving data in (smaller internal) chunks works.
+ */
+ void SimplePipePump()
+ {
+ CPPUNIT_ASSERT(backend);
+
+ TestPipe pipe1, pipe2;
+
+ bool res= pipe1.makePipe(pipe2);
+
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ CPPUNIT_ASSERT_EQUAL(true, pipe1.opened());
+ CPPUNIT_ASSERT_EQUAL(true, pipe2.opened());
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ std::string test_string= makeRandomAsciiString(256*1024);
+
+ pipe1.sendString(test_string);
+
+ res= backend->doOneStep(0);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ // do some backend cycles to empty the pipe:
+ for (int i=32; i-->0 && res && !pipe1.empty(); )
+ {
+ res= backend->doOneStep(100);
+ };
+
+ pipe1.close();
+ CPPUNIT_ASSERT_EQUAL(false, pipe1.opened());
+
+ // now read the remaining data until we recognize EOF:
+ for (int i=32; i-->0 && res && !pipe2.eof();)
+ {
+ res= backend->doOneStep(100);
+ }
+
+ CPPUNIT_ASSERT_EQUAL( test_string.size(), pipe2.m_received_string.size() );
+ CPPUNIT_ASSERT_EQUAL( test_string, pipe2.m_received_string );
+
+ CPPUNIT_ASSERT_EQUAL(true, pipe2.eof());
+ } // eo SimplePipePump
+
+
+
+ /**
+ * fork a subprocess (/bin/true) and test exit code.
+ */
+ void SimpleProcessTestBinTrue()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ TestProcess proc("/bin/true");
+
+ res= proc.startProcess();
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ res= backend->doOneStep(200);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ for(int i=20; i-->0 && proc.processState() != ProcessState::stopped;)
+ {
+ backend->doOneStep(10);
+ }
+
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::stopped), proc.processState() );
+ CPPUNIT_ASSERT_EQUAL( true, proc.eof() );
+ CPPUNIT_ASSERT_EQUAL( 0, proc.exitCode() );
+ } // eo SimpleProcessTestBinTrue
+
+
+ /**
+ * fork a subprocess (/bin/false) and test exit code.
+ */
+ void SimpleProcessTestBinFalse()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ TestProcess proc("/bin/false");
+
+ res= proc.startProcess();
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ res= backend->doOneStep(200);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ for(int i=20; i-->0 && proc.processState() != ProcessState::stopped;)
+ {
+ backend->doOneStep(10);
+ }
+
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::stopped), proc.processState() );
+ CPPUNIT_ASSERT_EQUAL( true, proc.eof() );
+ CPPUNIT_ASSERT_EQUAL( 1, proc.exitCode() );
+ DOUT("leave SimpleProcessTestBinFalse");
+ } // eo SimpleProcessTestBinFalse
+
+
+ /**
+ * fork an echo subprocess and read back the output.
+ */
+ void SimpleProcessTestEcho()
+ {
+ DOUT("enter SimpleProcessTestEcho");
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ TestProcess proc(
+ "/bin/echo",
+ TransientPushBackFiller<std::string, std::vector >()("Eine")("Zeichenkette")
+ );
+
+ res= proc.startProcess();
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ res= backend->doOneStep(200);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ for(int i=20; i-->0 && (proc.processState()!= ProcessState::stopped || !proc.eof());)
+ {
+ backend->doOneStep(10);
+ }
+
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::stopped), proc.processState() );
+ CPPUNIT_ASSERT_EQUAL( true, proc.eof() );
+ CPPUNIT_ASSERT_EQUAL( 0, proc.exitCode() );
+ CPPUNIT_ASSERT_EQUAL( std::string("Eine Zeichenkette\n"), proc.m_received_string);
+ } // eo SimpleProcessTestEcho
+
+
+
+ /**
+ * fork a bash subprocess, echo something on stderr and read back the output.
+ */
+ void SimpleProcessTestStderr()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ TestIO my_stderr;
+
+ TestProcess proc(
+ "/bin/bash",
+ TransientPushBackFiller<std::string, std::vector >()
+ ("-c")
+ ("echo Eine Zeichenkette >&2")
+ );
+
+ // start with a seperate io object for stderr.
+ DOUT("## start process");
+ res= proc.startProcess( &my_stderr );
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ CPPUNIT_ASSERT_EQUAL(true, my_stderr.opened());
+
+ DOUT("## do a backend step");
+ res= backend->doOneStep(200);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ // wait until process stopped and both io's signal EOF (or until the loop ends ;-) )
+ DOUT("## enter loop");
+ for(int i=17; i-->0 && (proc.processState()!= ProcessState::stopped || !proc.eof() || !my_stderr.eof());)
+ {
+ DOUT("## round i=" << i);
+ backend->doOneStep(10);
+ }
+ DOUT("## loop left");
+
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::stopped), proc.processState() );
+ CPPUNIT_ASSERT_EQUAL( true, proc.eof() );
+ CPPUNIT_ASSERT_EQUAL( true, my_stderr.eof() );
+ CPPUNIT_ASSERT_EQUAL( 0, proc.exitCode() );
+ CPPUNIT_ASSERT_EQUAL( std::string("Eine Zeichenkette\n"), my_stderr.m_received_string);
+ DOUT("leave Test SimpleProcessTestStderr");
+ } // eo SimpleProcessTestStderr
+
+
+
+ /**
+ * checks termination of process by signal and if the signal is returned.
+ */
+ void SignaledProcessTermination()
+ {
+ bool res;
+ CPPUNIT_ASSERT(backend);
+
+ TestProcess proc("/bin/sleep","2");
+ res= proc.startProcess();
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ res= backend->doOneStep(10);
+ CPPUNIT_ASSERT_EQUAL(true, res);
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::running), proc.processState() );
+
+ res= backend->doOneStep(50);
+
+ // now send the process an USR1 (which terminates the process)
+ res=proc.kill( Signal::USR1 );
+ CPPUNIT_ASSERT_EQUAL(true, res);
+
+ // give the backend a chance to process the termination event:
+ for(int i=30; i-->0 && proc.processState()!=ProcessState::stopped;) backend->doOneStep(10);
+
+ CPPUNIT_ASSERT_EQUAL( ProcessState(ProcessState::stopped), proc.processState() );
+ CPPUNIT_ASSERT_EQUAL( true, proc.eof() );
+ CPPUNIT_ASSERT_EQUAL( Signal::USR1 , proc.exitCode()>>8 );
+ } // eo SignaledProcessTermination
+
+
+
+ void CallOut1()
+ {
+ Counter count;
+
+ callOut( boost::bind(&Counter::advance, &count), 1 );
+ backend->doOneStep( 10 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ backend->doOneStep( 1100 );
+
+ CPPUNIT_ASSERT_EQUAL( 1, count.value );
+ } // eo CallOut1()
+
+
+
+ void CallOut2()
+ {
+ Counter count;
+
+ callOut( boost::bind(&Counter::advance, &count), 0.5 );
+ backend->doOneStep( 10 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ backend->doOneStep( 800 );
+
+ CPPUNIT_ASSERT_EQUAL( 1, count.value );
+ } // eo CallOut2()
+
+
+
+ void RemoveCallOut1()
+ {
+ Counter count;
+
+ CallOutId id= callOut( boost::bind(&Counter::advance, &count), 1 );
+ backend->doOneStep( 10 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ bool res1 = removeCallOut(id);
+ bool res2 = removeCallOut(id);
+
+ CPPUNIT_ASSERT_EQUAL( true, res1 );
+ CPPUNIT_ASSERT_EQUAL( false, res2 );
+
+ backend->doOneStep( 1100 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ } // eo RemoveCallOut1()
+
+
+
+ void FrozenCall_Thaw()
+ {
+ Counter count;
+
+ CallOutId id= frozenCall( boost::bind(&Counter::advance, &count), 1 );
+ backend->doOneStep( 10 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ id.thaw();
+
+ backend->doOneStep( 1100 );
+
+ CPPUNIT_ASSERT_EQUAL( 1, count.value );
+ } // eo FrozenCall_Thaw()
+
+
+
+ void FrozenCall_Decay()
+ {
+ Counter count;
+
+ CallOutId id= frozenCall( boost::bind(&Counter::advance, &count), 1 );
+ backend->doOneStep( 10 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ CPPUNIT_ASSERT_EQUAL( true, id.active() );
+ backend->doOneStep( 1100 );
+
+ CPPUNIT_ASSERT_EQUAL( 0, count.value );
+ CPPUNIT_ASSERT_EQUAL( false, id.active() );
+ } // eo FrozenCall_Decay()
+
+
+
+ void UnixSockets_ClientServer()
+ {
+ std::string path= getCheckFilepath("UDS_CS");
+
+ UnixIOSocketHolder server_holder;
+ UnixServerSocket< TestUnixIOSocket > server_port;
+ UnixIOSocketPtr server;
+ TestUnixIOSocket client0;
+ TestUnixIOSocket client1;
+
+ bool res1 = server_port.open(path, 0600);
+ CPPUNIT_ASSERT_EQUAL( true, res1 );
+
+ {
+ Stat stat(path,false);
+ CPPUNIT_ASSERT( stat.is_socket() );
+ CPPUNIT_ASSERT_EQUAL( 0600u, (stat.mode() & 0777));
+ }
+
+ server_port.setNewConnectionCallback(
+ boost::bind( &UnixIOSocketHolder::store, &server_holder, _1)
+ );
+
+ // open a first client
+ bool res2= client0.open(path);
+ CPPUNIT_ASSERT_EQUAL( true, res2 );
+
+ CPPUNIT_ASSERT_EQUAL(0u, server_holder.size() );
+ backendStep(5,1);
+ CPPUNIT_ASSERT_EQUAL(1u, server_holder.size() );
+ CPPUNIT_ASSERT( server_holder.get(0).get() );
+
+ client0.sendData("a simple test string.");
+ backendStep(3,2);
+
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("a simple test string."),
+ server_holder.get(0)->m_received_string
+ );
+ server_holder.get(0)->sendData("reply 1");
+ backendStep(3,2);
+ CPPUNIT_ASSERT_EQUAL( std::string("reply 1"), client0.m_received_string );
+
+ // open a second client
+ res2= client1.open(path);
+ CPPUNIT_ASSERT_EQUAL( true, res2 );
+ backendStep(5,1);
+ CPPUNIT_ASSERT_EQUAL(2u, server_holder.size() );
+ CPPUNIT_ASSERT( server_holder.get(1).get() );
+
+ server_holder.get(1)->sendData("::reply 2");
+ backendStep(3,2);
+ CPPUNIT_ASSERT_EQUAL( std::string("::reply 2"), client1.m_received_string );
+
+ client1.sendData("another simple test string. 124");
+ backendStep(3,2);
+
+ CPPUNIT_ASSERT_EQUAL(
+ std::string("another simple test string. 124"),
+ server_holder.get(1)->m_received_string
+ );
+
+ // close first client
+ client0.close();
+ CPPUNIT_ASSERT_EQUAL( false, server_holder.get(0)->eof() );
+ backendStep(3,2);
+ CPPUNIT_ASSERT_EQUAL( true, server_holder.get(0)->eof() );
+ server_holder.get(0)->close();
+
+ // close second connection from server side
+ CPPUNIT_ASSERT_EQUAL( false, client1.eof() );
+ server_holder.get(1)->close();
+ backendStep(3,2);
+ CPPUNIT_ASSERT_EQUAL( true, client1.eof() );
+ client1.close();
+ } // eo UnixSockets_ClientServer()
+
+
+ void Dummy()
+ {
+ using namespace std;
+ cout << endl << "Random strings:" << endl;
+ for (int i=10; i-->0;)
+ {
+ cout << " " << makeRandomAsciiString(70)<< endl;
+ }
+ } // eo Dummy
+
+
+}; // eo class TestSimpleioBasics
+
+
+CPPUNIT_TEST_SUITE_REGISTRATION(TestSimpleioBasics);