diff --git a/configure.ac b/configure.ac index 879ad15..f79ec64 100644 --- a/configure.ac +++ b/configure.ac @@ -1,890 +1,900 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) dnl inject zero as a "patch" component of the version if missing in tag; dnl care to bump "X.Y.Z-yank" template below upon each release very desirable AC_INIT([libqb], m4_esyscmd([build-aux/git-version-gen $(echo '$Format:%h??%D$'\ | sed -ne 's/^[$]Format:[^$]*[$]$/.tarball-version/p;tend'\ -e 's/.*tag: v\([^, ][^, ]*\).*/\1-yank/;tv'\ -e 's/^\([[:xdigit:]][[:xdigit:]]*\).*/1.9.0-yank\1/'\ -e ':v' -e 'w .snapshot-version' -e 'i\ .snapshot-version' -e ':end' -e 'q')\ 's/^\(v[0-9][0-9]*\.[0-9][0-9]*\)\([^.].*\)\?$/\1.0\2/']), [developers@clusterlabs.org]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_SRCDIR([lib/ringbuffer.c]) AC_CONFIG_HEADERS([include/config.h include/qb/qbconfig.h]) AC_USE_SYSTEM_EXTENSIONS AM_INIT_AUTOMAKE([-Wno-portability dist-xz subdir-objects]) dnl automake >= 1.11 offers --enable-silent-rules for suppressing the output from dnl normal compilation. When a failure occurs, it will then display the full dnl command line dnl Wrap in m4_ifdef to avoid breaking on older platforms m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) LT_PREREQ([2.2.6]) + +AC_CANONICAL_HOST + # --enable-new-dtags: Use RUNPATH instead of RPATH. # It is necessary to have this done before libtool does linker detection. # See also: https://github.com/kronosnet/kronosnet/issues/107 -AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags], - [AM_LDFLAGS=-Wl,--enable-new-dtags], - [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])]) +case "$host_os" in + solaris*) + AC_MSG_NOTICE([Skipping --enable-new-dtags check, Solaris' RPATH logic is like RUNPATH on other platforms]) + ;; + *) + AX_CHECK_LINK_FLAG([-Wl,--enable-new-dtags], + [AM_LDFLAGS=-Wl,--enable-new-dtags], + [AC_MSG_ERROR(["Linker support for --enable-new-dtags is required"])]) + ;; +esac AC_SUBST([AM_LDFLAGS]) saved_LDFLAGS="$LDFLAGS" LDFLAGS="$AM_LDFLAGS $LDFLAGS" LT_INIT LDFLAGS="$saved_LDFLAGS" AC_CONFIG_MACRO_DIR([m4]) AC_CANONICAL_HOST LT_INIT AC_LANG([C]) dnl Fix default variables - "prefix" variable if not specified if test "$prefix" = "NONE"; then prefix="/usr" if test "$localstatedir" = "\${prefix}/var"; then localstatedir="/var" fi if test "$sysconfdir" = "\${prefix}/etc"; then sysconfdir="/etc" fi if test "$libdir" = "\${exec_prefix}/lib"; then if test -e /usr/lib64; then libdir="/usr/lib64" else libdir="/usr/lib" fi fi fi if test "$srcdir" = "."; then AC_MSG_NOTICE([building in place srcdir:$srcdir]) AC_DEFINE([BUILDING_IN_PLACE], 1, [building in place]) else AC_MSG_NOTICE([building out of tree srcdir:$srcdir]) fi # Checks for programs. # check stolen from gnulib/m4/gnu-make.m4 if ! ${MAKE-make} --version /cannot/make/this >/dev/null 2>&1; then AC_MSG_ERROR([you don't seem to have GNU make; it is required]) fi AC_PROG_CXX AM_CONDITIONAL(HAVE_GXX, [test "x$GXX" = xyes]) AC_PROG_AWK AC_PROG_CC AC_PROG_CPP AM_PROG_CC_C_O m4_version_prereq([2.70], [:], [AC_PROG_CC_C99]) if test "x$ac_cv_prog_cc_c99" = "xno"; then AC_MSG_ERROR(["C99 support is required"]) fi AC_PROG_INSTALL AC_PROG_LN_S AC_PROG_MAKE_SET AC_CHECK_PROGS([PKGCONFIG], [pkg-config]) AC_CHECK_PROGS([DOXYGEN], [doxygen]) AM_CONDITIONAL(HAVE_DOXYGEN, test -n "${DOXYGEN}") AC_CHECK_TOOLS([NM], [eu-nm nm], [:]) AC_CHECK_TOOLS([READELF], [eu-readelf readelf], [:]) AM_PATH_PYTHON([2.6],, [:]) AM_CONDITIONAL([HAVE_PYTHON], [test "$PYTHON" != :]) ## local helper functions # this function checks if CC support options passed as # args. Global CFLAGS are ignored during this test. cc_supports_flag() { BACKUP="$CPPFLAGS" CPPFLAGS="$CPPFLAGS $@ -Werror" AC_MSG_CHECKING([whether $CC supports "$@"]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([])], [RC=0; AC_MSG_RESULT([yes])], [RC=1; AC_MSG_RESULT([no])]) CPPFLAGS="$BACKUP" return $RC } ## cleanup AC_MSG_NOTICE(Sanitizing prefix: ${prefix}) case $prefix in NONE) prefix=/usr/local;; esac AC_MSG_NOTICE(Sanitizing exec_prefix: ${exec_prefix}) case $exec_prefix in NONE) exec_prefix=$prefix;; prefix) exec_prefix=$prefix;; esac # Checks for libraries. AX_PTHREAD(,[AC_MSG_ERROR([POSIX threads support is required])]) AX_SAVE_FLAGS AC_SEARCH_LIBS([dlopen],[dl],,[AC_MSG_ERROR([cannot find dlopen() function])]) AC_SUBST([dlopen_LIBS],[$LIBS]) AX_RESTORE_FLAGS AX_SAVE_FLAGS AC_SEARCH_LIBS([socket], [socket],,[AC_MSG_ERROR([cannot find socket() function])]) AC_SUBST([socket_LIBS],[$LIBS]) AX_RESTORE_FLAGS AX_SAVE_FLAGS AC_SEARCH_LIBS([gethostbyname], [nsl],,[AC_MSG_ERROR([cannot find gethostbyname() function])]) AC_SUBST([nsl_LIBS],[$LIBS]) AX_RESTORE_FLAGS AX_SAVE_FLAGS AC_SEARCH_LIBS([clock_gettime], [rt],,[AC_MSG_ERROR([cannot find clock_gettime() function])]) AC_SUBST([rt_LIBS],[$LIBS]) AX_RESTORE_FLAGS USE_JOURNAL="no" PKG_CHECK_MODULES([SYSTEMD], [libsystemd], [have_systemd=yes],[have_systemd=no]) if test "x$enable_systemd_journal" = "xyes" ; then if test "x$have_systemd" = "xyes" ; then AC_DEFINE_UNQUOTED(USE_JOURNAL, 1, [Use systemd journal logging]) USE_JOURNAL="yes" else echo "systemd libraries not found, will just use syslog" fi fi # look for testing harness "check" PKG_CHECK_MODULES([CHECK], [check >= 0.9.4],[with_check=yes],[with_check=no]) AM_CONDITIONAL(HAVE_CHECK, test "${with_check}" = "yes") # look for GLIB (used for testing integration) PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.0, have_glib=yes, have_glib=no) AM_CONDITIONAL(HAVE_GLIB, test x$have_glib = xyes) AC_SUBST(GLIB_CFLAGS) AC_SUBST(GLIB_LIBS) if test x"$have_glib" = xyes; then AC_DEFINE_UNQUOTED([HAVE_GLIB], [1], [We have glib]) fi # For building doxygen2man and man pages PKG_CHECK_MODULES([libxml], [libxml-2.0]) # if we are not cross-compiling, we can use the locally built # version of doxygen2man, otherwise we can look for # a locally installed version. If neither are usable, then # donĀ“t build the man pages if test "x$cross_compiling" = "xno"; then AM_CONDITIONAL([BUILD_MAN], [true]) DOXYGEN2MAN="\$(abs_builddir)/../doxygen2man/doxygen2man" else AC_CHECK_PROGS([DOXYGEN2MAN], [doxygen2man]) if test "x$DOXYGEN2MAN" = "x"; then AM_CONDITIONAL([BUILD_MAN], [false]) else AM_CONDITIONAL([BUILD_MAN], [true]) fi fi AC_SUBST(DOXYGEN2MAN) # Checks for header files. AC_HEADER_SYS_WAIT AC_CHECK_HEADERS([arpa/inet.h link.h fcntl.h inttypes.h limits.h netinet/in.h \ stdint.h stddef.h stdlib.h string.h strings.h \ dlfcn.h time.h sys/time.h sys/types.h sys/stat.h \ sys/param.h sys/socket.h sys/time.h sys/poll.h sys/epoll.h \ sys/uio.h sys/event.h sys/sockio.h sys/un.h sys/resource.h \ syslog.h errno.h unistd.h sys/mman.h \ sys/sem.h sys/ipc.h sys/msg.h netdb.h]) # Checks for typedefs, structures, and compiler characteristics. AC_TYPE_UID_T AC_C_INLINE AC_TYPE_INT32_T AC_TYPE_INT64_T AC_TYPE_INT8_T AC_TYPE_MODE_T AC_TYPE_PID_T AC_TYPE_SIZE_T AC_TYPE_SSIZE_T AC_TYPE_UINT64_T AC_TYPE_UINT32_T AC_TYPE_UINT16_T AC_TYPE_UINT8_T AC_CHECK_MEMBER([struct sockaddr_un.sun_len], [AC_DEFINE([HAVE_STRUCT_SOCKADDR_UN_SUN_LEN], 1, [Define to 1 if struct sockaddr_un has a member sun_len])], [], [#include ]) AC_MSG_CHECKING(looking for union semun in sys/sem.h) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include #include #include ]], [[union semun arg; semctl(0, 0, 0, arg);]])], [ AC_MSG_RESULT([yes]) AC_DEFINE_UNQUOTED([HAVE_SEMUN], 1, [Define to 1 if you have union semun.]) ], [ AC_MSG_RESULT([no]) ] ) # Checks for library functions AX_SAVE_FLAGS LIBS="$LIBS $rt_LIBS" AC_CHECK_FUNCS([clock_gettime]) AX_RESTORE_FLAGS AC_FUNC_CHOWN AC_FUNC_FORK AC_FUNC_MMAP AC_FUNC_STRERROR_R AC_CHECK_FUNCS([alarm fsync fdatasync ftruncate posix_fallocate \ gettimeofday localtime localtime_r \ memset munmap socket \ strchr strrchr strdup strstr strcasecmp \ poll epoll_create epoll_create1 kqueue \ random rand getrlimit sysconf \ getpeerucred getpeereid \ openat unlinkat]) AX_SAVE_FLAGS CFLAGS="$CFLAGS $PTHREAD_CFLAGS" LIBS="$LIBS $PTHREAD_LIBS" AC_CHECK_FUNCS([pthread_spin_lock pthread_setschedparam \ pthread_mutexattr_setpshared \ pthread_condattr_setpshared \ sem_timedwait semtimedop]) AX_RESTORE_FLAGS # Checks for defined macros AC_MSG_CHECKING(for MSG_NOSIGNAL) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ int f = MSG_NOSIGNAL; ]])], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_MSG_NOSIGNAL, 1, [Define this symbol if you have MSG_NOSIGNAL])], [ AC_MSG_RESULT(no)]) AC_MSG_CHECKING(for SO_NOSIGPIPE ) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include ]], [[ int f = SO_NOSIGPIPE; ]])], [ AC_MSG_RESULT(yes) AC_DEFINE(HAVE_SO_NOSIGPIPE, 1, [Define this symbol if you have SO_NOSIGPIPE]) ], [ AC_MSG_RESULT(no)]) AC_MSG_CHECKING([for RTLD_NEXT]) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include ]], [[ void *h = RTLD_NEXT; ]])], [ AC_MSG_RESULT([yes]) AC_DEFINE([HAVE_FAILURE_INJECTION], 1, [have failure injection]) have_RTLD_NEXT=yes ], [ AC_MSG_RESULT([no])]) AM_CONDITIONAL(HAVE_SEM_TIMEDWAIT, [test "x$ac_cv_func_sem_timedwait" = xyes]) AM_CONDITIONAL(HAVE_EPOLL, [test "x$ac_cv_func_epoll_create" = xyes]) AM_CONDITIONAL(HAVE_POLL, [test "x$ac_cv_func_poll" = xyes]) AM_CONDITIONAL(HAVE_KQUEUE, [test "x$ac_cv_func_kqueue" = xyes]) AM_CONDITIONAL(HAVE_FAILURE_INJECTION, [test "x$have_RTLD_NEXT" = xyes]) AC_CONFIG_LIBOBJ_DIR(lib) AC_REPLACE_FUNCS(strlcpy strlcat strchrnul) ## local defines PACKAGE_FEATURES="" if test x$ac_cv_func_epoll_create = xyes; then PACKAGE_FEATURES="$PACKAGE_FEATURES epoll" fi nongcc_memory_barrier_needed=no arch_force_shmlba=no file_sync=fdatasync AC_MSG_CHECKING([for architecture in ${host_cpu}]) case $host_cpu in sparc*) AC_MSG_RESULT([sparc]) AC_DEFINE_UNQUOTED([QB_ARCH_SPARC], [1], [sparc]) nongcc_memory_barrier_needed=yes arch_force_shmlba=yes ;; alpha*) AC_MSG_RESULT([alpha]) AC_DEFINE_UNQUOTED([QB_ARCH_ALPHA], [1], [alpha]) nongcc_memory_barrier_needed=yes ;; powerpc*) AC_MSG_RESULT([powerpc]) AC_DEFINE_UNQUOTED([QB_ARCH_POWERPC], [1], [powerpc]) nongcc_memory_barrier_needed=yes arch_force_shmlba=yes ;; ia64) AC_MSG_RESULT([ia64]) AC_DEFINE_UNQUOTED([QB_ARCH_IA64], [1], [ia64]) nongcc_memory_barrier_needed=yes ;; arm*) AC_MSG_RESULT([arm]) AC_DEFINE_UNQUOTED([QB_ARCH_ARM], [1], [arm]) arch_force_shmlba=yes ;; hppa*) AC_MSG_RESULT([hppa]) AC_DEFINE_UNQUOTED([QB_ARCH_HPPA], [1], [hppa]) ;; mips*) AC_MSG_RESULT([ia64]) AC_DEFINE_UNQUOTED([QB_ARCH_MIPS], [1], [mips]) arch_force_shmlba=yes ;; *) AC_MSG_RESULT([${host_cpu}]) ;; esac if test $arch_force_shmlba = yes; then AC_DEFINE_UNQUOTED([QB_FORCE_SHM_ALIGN], [1], [shared and fixed mmap must align on 16k]) fi # OS detection # THIS SECTION MUST DIE! CP=cp AC_MSG_CHECKING([for os in ${host_os}]) case "$host_os" in *linux*) AC_DEFINE_UNQUOTED([QB_LINUX], [1], [Compiling for Linux platform]) AC_MSG_RESULT([Linux]) ;; *cygwin*) AC_DEFINE_UNQUOTED([QB_CYGWIN], [1], [Compiling for Cygwin platform]) nongcc_memory_barrier_needed=yes gcc_has_builtin_sync_operations=no AC_MSG_RESULT([Cygwin]) ;; darwin*) AC_DEFINE_UNQUOTED([QB_DARWIN], [1], [Compiling for Darwin platform]) CP=rsync dnl Attribute section appears to work here but fails later with: dnl cc1: error in backend: Global variable 'descriptor.4902' dnl has an invalid section specifier '__verbose': mach-o dnl section specifier requires a segment and section dnl separated by a comma AC_DEFINE_UNQUOTED([DISABLE_POSIX_THREAD_PROCESS_SHARED], [1], [Disable _POSIX_THREAD_PROCESS_SHARED]) AC_MSG_RESULT([Darwin]) ;; *bsd*) AC_DEFINE_UNQUOTED([QB_BSD], [1], [Compiling for BSD platform]) case "$host_os" in *netbsd*) AC_DEFINE_UNQUOTED([UNIX_PATH_MAX], [103], [Unix path length]) ;; *openbsd*) AC_DEFINE_UNQUOTED([UNIX_PATH_MAX], [104], [Unix path length]) ;; esac AC_MSG_RESULT([BSD]) ;; *solaris*) AC_DEFINE_UNQUOTED(DISABLE_IPC_SHM, 1, [Disable shared mem ipc]) AC_DEFINE_UNQUOTED([QB_SOLARIS], [1], [Compiling for Solaris platform]) CP=rsync AC_MSG_RESULT([Solaris]) ;; *gnu*) AC_DEFINE_UNQUOTED([QB_GNU], [1], [Compiling for GNU/Hurd platform]) AC_MSG_RESULT([GNU]) ;; *) AC_MSG_ERROR([Unsupported OS? hmmmm]) ;; esac dnl break on first hit, fallthrough otherwise, until empty or unsupported dnl string reached; output QB_FILE_SYNC macro operates on a file descriptor while : ; do case ${file_sync} in fdatasync) test "x${ac_cv_func_fdatasync}" = "xno" || break file_sync=fsync dnl Darwin provides the function but doesn't have it in headers case "$host_os" in darwin*) AH_BOTTOM([int fdatasync(int fildes);]) ;; esac ;; fsync) test "x${ac_cv_func_fsync}" = "xno" || break file_sync=;; "") break;; *) AC_MSG_ERROR([Cannot select file sync method]);; esac done if test "x${file_sync}" != x; then AC_DEFINE_UNQUOTED([QB_FILE_SYNC(fd)], [${file_sync}(fd)], [File sync method]) else AC_MSG_WARN([No file sync method applicable!]) AC_DEFINE([QB_FILE_SYNC(x)], [], [File sync method]) fi AC_MSG_CHECKING([whether GCC supports builtin sync intrinsics]) if test -z "$gcc_has_builtin_sync_operations"; then gcc_has_builtin_sync_operations=no if test x"$GCC" = xyes && test x$have_mingw != xyes; then AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i; __sync_synchronize (); __sync_bool_compare_and_swap (&i, 0, 1); __sync_fetch_and_add (&i, 1); ]])], [gcc_has_builtin_sync_operations=yes], [gcc_has_builtin_sync_operations=no]) fi fi AC_MSG_RESULT($gcc_has_builtin_sync_operations) AM_CONDITIONAL(HAVE_GCC_BUILTINS_FOR_SYNC_OPERATIONS, [test "x$gcc_has_builtin_sync_operations" = xyes]) if test "x$gcc_has_builtin_sync_operations" = xyes; then AC_DEFINE_UNQUOTED(HAVE_GCC_BUILTINS_FOR_SYNC_OPERATIONS, 1, [have builtin sync operations]) fi # __atomic_XXX AC_MSG_CHECKING([whether GCC supports builtin atomic intrinsics]) if test -z "$gcc_has_builtin_atomic_operations"; then gcc_has_builtin_atomic_operations=no if test x"$GCC" = xyes && test x$have_mingw != xyes; then AC_LINK_IFELSE([AC_LANG_PROGRAM([[]], [[int i; __atomic_load_n(&i, __ATOMIC_ACQUIRE); __atomic_exchange_n(&i, 0, __ATOMIC_RELEASE); ]])], [gcc_has_builtin_atomic_operations=yes], [gcc_has_builtin_atomic_operations=no]) fi fi AC_MSG_RESULT($gcc_has_builtin_atomic_operations) AM_CONDITIONAL(HAVE_GCC_BUILTINS_FOR_ATOMIC_OPERATIONS, [test "x$gcc_has_builtin_atomic_operations" = xyes]) if test "x$gcc_has_builtin_atomic_operations" = xyes; then AC_DEFINE_UNQUOTED(HAVE_GCC_BUILTINS_FOR_ATOMIC_OPERATIONS, 1, [have builtin atomic operations]) fi AC_MSG_CHECKING([whether atomics need memory barrier]) if test -n "$ac_cv_atomic_need_memory_barrier"; then memory_barrier_needed=$ac_cv_atomic_need_memory_barrier else if test x$gcc_has_builtin_sync_operations = xyes; then memory_barrier_needed=yes PACKAGE_FEATURES="$PACKAGE_FEATURES gcc__sync" else memory_barrier_needed=$nongcc_memory_barrier_needed AC_MSG_WARN([-----------------------------]) AC_MSG_WARN([You have gcc but not __sync_bool_compare_and_swap]) AC_MSG_WARN([try CFLAGS="-march= -mtune=native" ./configure]) AC_MSG_WARN([-----------------------------]) fi fi AC_MSG_RESULT($memory_barrier_needed) if test x"$memory_barrier_needed" != xno; then AC_DEFINE_UNQUOTED(QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED, 1, [need atomic memory barrier]) fi LINT_FLAGS="-weak -D__gnuc_va_list=va_list -D__attribute\(x\)= \ -badflag -fcnuse -syntax -unrecog -sysunrecog -warnposix \ +ignoresigns +matchanyintegral +posixlib \ +showscan +showsummary" # local options AC_ARG_ENABLE([ansi], [AS_HELP_STRING([--enable-ansi],[force to build with ANSI standards])]) AC_ARG_ENABLE([fatal-warnings], [AS_HELP_STRING([--enable-fatal-warnings],[enable fatal warnings])]) AC_ARG_ENABLE([debug], [AS_HELP_STRING([--enable-debug],[enable debug build])]) AC_ARG_WITH([sanitizers], [AS_HELP_STRING([--with-sanitizers=...,...], [enable SANitizer build, do *NOT* use for production. Only ASAN/UBSAN/TSAN are currently supported])], [ SANITIZERS="$withval" ], [ SANITIZERS="" ]) AC_ARG_ENABLE([coverage], [AS_HELP_STRING([--enable-coverage],[coverage analysis of the codebase])]) AC_ARG_ENABLE([interlib-deps], [AS_HELP_STRING([--disable-interlib-deps], [disable inter-library dependencies (might break builds)])]) AC_ARG_ENABLE([slow-tests], [AS_HELP_STRING([--enable-slow-tests],[build and run slow tests])]) AC_ARG_WITH([socket-dir], [AS_HELP_STRING([--with-socket-dir=DIR],[socket directory @<:@LOCALSTATEDIR/run@:>@])], [ SOCKETDIR="$withval" ], [ SOCKETDIR="$localstatedir/run" ]) AC_ARG_ENABLE([systemd-journal], [AS_HELP_STRING([--enable-systemd-journal],[Allow use of systemd journal instead of syslog])]) AC_ARG_WITH([force-sockets-config-file], [AS_HELP_STRING([--with-force-sockets-config-file=FILE],[config file to force IPC to use filesystem sockets (Linux & Cygwin only) @<:@SYSCONFDIR/libqb/force-filesystem-sockets@:>@])], [ FORCESOCKETSFILE="$withval" ], [ FORCESOCKETSFILE="$sysconfdir/libqb/force-filesystem-sockets" ]) AC_ARG_ENABLE([tests], [AS_HELP_STRING([--disable-tests],[disable tests])],, [ enable_tests="yes" ]) AM_CONDITIONAL([ENABLE_TESTS], [test x$enable_tests = xyes]) AC_ARG_ENABLE([install-tests], [AS_HELP_STRING([--enable-install-tests],[install tests])],, [ enable_install_tests="no" ]) AM_CONDITIONAL([INSTALL_TESTS], [test x$enable_install_tests = xyes]) AC_ARG_WITH([testdir], [AS_HELP_STRING([--with-testdir=DIR],[path to /usr/lib../libqb/tests/ dir where to install the test suite])], [ TESTDIR="$withval" ], [ TESTDIR="$libdir/libqb/tests" ]) AC_SUBST([TESTDIR]) AC_SUBST(CP) # *FLAGS handling goes here ENV_CFLAGS="$CFLAGS" ENV_CPPFLAGS="$CPPFLAGS" ENV_LDFLAGS="$LDFLAGS" # debug build stuff if test "x${enable_debug}" = xyes; then AC_DEFINE_UNQUOTED([DEBUG], [1], [Compiling Debugging code]) OPT_CFLAGS="-O0" if test "x${GCC}" = xyes; then GDB_FLAGS="-ggdb3" else GDB_FLAGS="-g" fi PACKAGE_FEATURES="$PACKAGE_FEATURES debug" fi # extra warnings EXTRA_WARNINGS="" WARNLIST=" all extra unused shadow missing-prototypes missing-declarations suggest-attribute=noreturn suggest-attribute=format property-attribute-mismatch strict-prototypes pointer-arith write-strings cast-align bad-function-cast missing-format-attribute float-equal format=2 format-signedness shift-overflow shift-overflow=2 overlength-strings redundent-decls init-self uninitialized unknown-pragmas no-unused-parameter unused-const-variable no-format-nonliteral no-format-truncation no-sign-compare " # at least no-sign-compare assumed temporary! for j in $WARNLIST; do if cc_supports_flag -W$j; then EXTRA_WARNINGS="$EXTRA_WARNINGS -W$j"; fi done # warnings suppression gcc_format_complaints=no if test x"$GCC" = xyes && cc_supports_flag -Wmissing-format-attribute; then gcc_format_complaints=yes AC_DEFINE([HAVE_GCC_MISSING_FORMAT_ATTRIBUTE], [], [gcc supports -Wmissing-format-attribute]) fi if test x"$GCC" = xyes && cc_supports_flag -Wsuggest-attribute=format; then gcc_format_complaints=yes AC_DEFINE([HAVE_GCC_SUGGEST_ATTRIBUTE_FORMAT], [], [gcc supports -Wsuggest-attribute=format]) fi dnl pretend GCC (<4.6) is not capable of format complaints when it does not dnl support diagnostic push/pop pragmas (cannot track state reliably, then) if test x"$gcc_format_complaints" = xyes; then backup_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS -Werror" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[ #pragma GCC diagnostic push #pragma GCC diagnostic pop ]])], AC_DEFINE([HAVE_GCC_FORMAT_COMPLAINTS], [], [gcc can complain about missing format attribute])) CFLAGS="$backup_CFLAGS" fi # --- coverage --- if test "x${enable_coverage}" = xyes && \ cc_supports_flag -ftest-coverage && \ cc_supports_flag -fprofile-arcs ; then AC_MSG_NOTICE([Enabling Coverage (enable -O0 by default)]) OPT_CFLAGS="-O0" COVERAGE_CFLAGS="-ftest-coverage -fprofile-arcs" COVERAGE_LDFLAGS="-ftest-coverage -fprofile-arcs" PACKAGE_FEATURES="$PACKAGE_FEATURES coverage" else COVERAGE_CFLAGS="" COVERAGE_LDFLAGS="" fi # --- inter-library dependencies --- # because of debian/ubuntu swimming against the stream # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=702737, # override the libtool variable by force because the current # arrangement relies on transitive dependency propagation AC_MSG_NOTICE([Enabling inter-library dependencies: $enable_interlib_deps]) if test "x${enable_interlib_deps}" = xno; then link_all_deplibs=no else link_all_deplibs=yes fi # --- slow tests --- if test "x${enable_slow_tests}" = xyes ; then AC_DEFINE([HAVE_SLOW_TESTS], 1,[have slow tests]) AC_MSG_NOTICE([Enabling Slow tests]) fi AM_CONDITIONAL(HAVE_SLOW_TESTS, [test "x${enable_slow_tests}" = xyes]) AC_SUBST(HAVE_SLOW_TESTS) # --- ansi --- if test "x${enable_ansi}" = xyes && \ cc_supports_flag -std=iso9899:199409 ; then AC_MSG_NOTICE([Enabling ANSI Compatibility]) ANSI_CPPFLAGS="-ansi -D_GNU_SOURCE -DANSI_ONLY" PACKAGE_FEATURES="$PACKAGE_FEATURES ansi" else ANSI_CPPFLAGS="" fi # --- fatal warnings --- if test "x${enable_fatal_warnings}" = xyes && \ cc_supports_flag -Werror ; then AC_MSG_NOTICE([Enabling Fatal Warnings (-Werror)]) WERROR_CFLAGS="-Werror" PACKAGE_FEATURES="$PACKAGE_FEATURES fatal-warnings" else WERROR_CFLAGS="" fi # --- ASAN/UBSAN/TSAN (see man gcc) --- # when using SANitizers, we need to pass the -fsanitize.. # to both CFLAGS and LDFLAGS. The CFLAGS/LDFLAGS must be # specified as first in the list or there will be runtime # issues (for example user has to LD_PRELOAD asan for it to work # properly). if test -n "${SANITIZERS}"; then SANITIZERS=$(echo $SANITIZERS | sed -e 's/,/ /g') for SANITIZER in $SANITIZERS; do case $SANITIZER in asan|ASAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=address" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=address -lasan" AC_CHECK_LIB([asan],[main],,AC_MSG_ERROR([Unable to find libasan])) ;; ubsan|UBSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=undefined" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=undefined -lubsan" AC_CHECK_LIB([ubsan],[main],,AC_MSG_ERROR([Unable to find libubsan])) ;; tsan|TSAN) SANITIZERS_CFLAGS="$SANITIZERS_CFLAGS -fsanitize=thread" SANITIZERS_LDFLAGS="$SANITIZERS_LDFLAGS -fsanitize=thread -ltsan" AC_CHECK_LIB([tsan],[main],,AC_MSG_ERROR([Unable to find libtsan])) ;; esac done fi # final build of *FLAGS CFLAGS="$SANITIZERS_CFLAGS $ENV_CFLAGS $OPT_CFLAGS $GDB_FLAGS \ $COVERAGE_CFLAGS $EXTRA_WARNINGS $WERROR_CFLAGS" CPPFLAGS="$ENV_CPPFLAGS $ANSI_CPPFLAGS" LDFLAGS="$SANITIZERS_LDFLAGS $ENV_LDFLAGS $COVERAGE_LDFLAGS" if test -f /usr/share/dict/words ; then HAVE_DICT_WORDS=yes AC_DEFINE([HAVE_DICT_WORDS], 1, "Have /usr/share/dict/words") fi AM_CONDITIONAL([HAVE_DICT_WORDS], [test "x$HAVE_DICT_WORDS" = xyes]) # substitute what we need: AC_SUBST([SOCKETDIR]) AC_SUBST([LINT_FLAGS]) AC_SUBST([FORCESOCKETSFILE]) AC_DEFINE_UNQUOTED([SOCKETDIR], "$(eval echo ${SOCKETDIR})", [Socket directory]) AC_DEFINE_UNQUOTED([LOCALSTATEDIR], "$(eval echo ${localstatedir})", [localstate directory]) AC_DEFINE_UNQUOTED([PACKAGE_FEATURES], "${PACKAGE_FEATURES}", [quarterback built-in features]) AC_DEFINE_UNQUOTED([FORCESOCKETSFILE], "$(eval echo ${FORCESOCKETSFILE})", [for sockets config file]) # version parsing (for qbconfig.h) AC_DEFINE_UNQUOTED([QB_VER_MAJOR], [$(echo "${VERSION}" \ | sed -e 's|^\([[0-9]][[0-9]]*\).*|\1|' \ -e t -e 's|.*|1|')], [libqb major version]) AC_DEFINE_UNQUOTED([QB_VER_MINOR], [$(echo "${VERSION}" \ | sed -e 's|^[[0-9]][[0-9]]*\.\([[0-9]][[0-9]]*\).*|\1|' \ -e t -e 's|.*|0|')], [libqb minor version]) AC_DEFINE_UNQUOTED([QB_VER_MICRO], [$(echo "${VERSION}" \ | sed -e 's|^[[0-9]][[0-9]]*\.[[0-9]][[0-9]]*\.\([[0-9]][[0-9]]*\).*|\1|' \ -e t -e 's|.*|0|')], [libqb patch version]) AC_DEFINE_UNQUOTED([QB_VER_REST], [$(echo "${VERSION}" \ | sed -e 's|^[[0-9]][[0-9]]*\(\.[[0-9]][[0-9]]*\)\{0,2\}\(.*\)|"\2"|' \ -e t -e 's|.*|""|')], [libqb patch version]) AX_PROG_DATE AS_IF([test "$ax_cv_prog_date_gnu_date:$ax_cv_prog_date_gnu_utc" = yes:yes], [UTC_DATE_AT="date -u -d@"], [AS_IF([test "x$ax_cv_prog_date_bsd_date" = xyes], [UTC_DATE_AT="date -u -r"], [AC_MSG_ERROR([date utility unable to convert epoch to UTC])])]) AC_SUBST([UTC_DATE_AT]) AC_ARG_VAR([SOURCE_EPOCH],[last modification date of the source]) AC_MSG_NOTICE([trying to determine source epoch]) AC_MSG_CHECKING([for source epoch in \$SOURCE_EPOCH]) AS_IF([test -n "$SOURCE_EPOCH"], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch in source_epoch file]) AS_IF([test -e "$srcdir/source_epoch"], [read SOURCE_EPOCH <"$srcdir/source_epoch" AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch baked in by gitattributes export-subst]) SOURCE_EPOCH='$Format:%ct$' # template for rewriting by git-archive AS_CASE([$SOURCE_EPOCH], [?Format:*], # was not rewritten [AC_MSG_RESULT([no]) AC_MSG_CHECKING([for source epoch in \$SOURCE_DATE_EPOCH]) AS_IF([test "x$SOURCE_DATE_EPOCH" != x], [SOURCE_EPOCH="$SOURCE_DATE_EPOCH" AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no]) AC_MSG_CHECKING([whether git log can provide a source epoch]) SOURCE_EPOCH=f${SOURCE_EPOCH#\$F} # convert into git log --pretty format SOURCE_EPOCH=$(cd "$srcdir" && git log -1 --pretty=${SOURCE_EPOCH%$} 2>/dev/null) AS_IF([test -n "$SOURCE_EPOCH"], [AC_MSG_RESULT([yes])], [AC_MSG_RESULT([no, using current time and breaking reproducibility]) SOURCE_EPOCH=$(date +%s)])])], [AC_MSG_RESULT([yes])] )]) ]) AC_MSG_NOTICE([using source epoch $($UTC_DATE_AT$SOURCE_EPOCH +'%F %T %Z')]) AC_CONFIG_FILES([Makefile include/Makefile include/qb/Makefile lib/Makefile lib/libqb.pc tools/Makefile tests/Makefile tests/test.conf examples/Makefile doxygen2man/Makefile docs/Makefile docs/man.dox lib/qblog_script.la:lib/qblog_script.la.in]) AC_OUTPUT AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE configuration:]) AC_MSG_RESULT([ Version = ${VERSION}]) AC_MSG_RESULT([ Prefix = ${prefix}]) AC_MSG_RESULT([ Executables = ${sbindir}]) AC_MSG_RESULT([ Man pages = ${mandir}]) AC_MSG_RESULT([ Doc dir = ${docdir}]) AC_MSG_RESULT([ Libraries = ${libdir}]) AC_MSG_RESULT([ Header files = ${includedir}]) AC_MSG_RESULT([ Arch-independent files = ${datadir}]) AC_MSG_RESULT([ State information = ${localstatedir}]) AC_MSG_RESULT([ System configuration = ${sysconfdir}]) AC_MSG_RESULT([ SOCKETDIR = ${SOCKETDIR}]) AC_MSG_RESULT([ Features = ${PACKAGE_FEATURES}]) AC_MSG_RESULT([ Use systemd journal = ${USE_JOURNAL}]) AC_MSG_RESULT([]) AC_MSG_RESULT([$PACKAGE build info:]) AC_MSG_RESULT([ Optimization = ${OPT_CFLAGS}]) AC_MSG_RESULT([ Debug options = ${GDB_CFLAGS}]) AC_MSG_RESULT([ Extra compiler warnings = ${EXTRA_WARNING}]) AC_MSG_RESULT([ Env. defined CFLAG = ${ENV_CFLAGS}]) AC_MSG_RESULT([ Env. defined CPPFLAGS = ${ENV_CPPFLAGS}]) AC_MSG_RESULT([ Env. defined LDFLAGS = ${ENV_LDFLAGS}]) AC_MSG_RESULT([ ANSI defined CPPFLAGS = ${ANSI_CPPFLAGS}]) AC_MSG_RESULT([ Coverage CFLAGS = ${COVERAGE_CFLAGS}]) AC_MSG_RESULT([ Coverage LDFLAGS = ${COVERAGE_LDFLAGS}]) AC_MSG_RESULT([ Fatal War. CFLAGS = ${WERROR_CFLAGS}]) AC_MSG_RESULT([ Final CFLAGS = ${CFLAGS}]) AC_MSG_RESULT([ Final CPPFLAGS = ${CPPFLAGS}]) AC_MSG_RESULT([ Final LDFLAGS = ${LDFLAGS}]) diff --git a/doxygen2man/doxygen2man.c b/doxygen2man/doxygen2man.c index 3197967..f8e1f2b 100644 --- a/doxygen2man/doxygen2man.c +++ b/doxygen2man/doxygen2man.c @@ -1,1394 +1,1396 @@ /* * Copyright (C) 2018-2021 Red Hat, Inc. All rights reserved. * * Author: Christine Caulfield * * This software licensed under GPL-2.0+ */ /* * NOTE: this code is very rough, it does the bare minimum to parse the * XML out from doxygen and is probably very fragile to changes in that XML * schema. It probably leaks memory all over the place too. * * In its favour, it *does* generate nice man pages and should only be run very ocasionally */ #define _DEFAULT_SOURCE #define _BSD_SOURCE #define _GNU_SOURCE #define _XOPEN_SOURCE #define _XOPEN_SOURCE_EXTENDED +#define _XPG4_2 +#define _XPG7 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cstring.h" /* * This isn't a maximum size, it just defines how long a parameter * type can get before we decide it's not worth lining everything up. * It's mainly to stop function pointer types (which can get VERY long because * of all *their* parameters) making everything else 'line-up' over separate lines */ #define LINE_LENGTH 80 /* Similar - but for structure member comments */ #define STRUCT_COMMENT_LENGTH 50 static int print_ascii = 1; static int print_man = 0; static int print_params = 0; static int print_general = 0; static int num_functions = 0; static int quiet = 0; static int use_header_copyright = 0; static const char *man_section="3"; static const char *package_name="Package"; static const char *header="Programmer's Manual"; static const char *company="Red Hat"; static const char *output_dir="./"; static const char *xml_dir = "./xml/"; static const char *xml_file; static const char *manpage_date = NULL; static const char *headerfile = NULL; static const char *header_prefix = ""; static const char *header_src_dir = "./"; static char header_copyright[256] = "\0"; static long manpage_year = LONG_MIN; static long start_year = 2010; static struct qb_list_head params_list; static struct qb_list_head retval_list; static qb_map_t *function_map; static qb_map_t *structures_map; static qb_map_t *used_structures_map; struct param_info { char *paramname; char *paramtype; char *paramdesc; struct param_info *next; struct qb_list_head list; }; struct struct_info { enum {STRUCTINFO_STRUCT, STRUCTINFO_ENUM} kind; char *structname; char *description; char *brief_description; struct qb_list_head params_list; /* our params */ struct qb_list_head list; }; static cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl); static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg); static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext); static void man_print_long_string(FILE *manfile, char *text); static void free_paraminfo(struct param_info *pi) { free(pi->paramname); free(pi->paramtype); free(pi->paramdesc); free(pi); } static char *get_attr(xmlNode *node, const char *tag) { xmlAttr *this_attr; for (this_attr = node->properties; this_attr; this_attr = this_attr->next) { if (this_attr->type == XML_ATTRIBUTE_NODE && strcmp((char *)this_attr->name, tag) == 0) { return strdup((char *)this_attr->children->content); } } return NULL; } static cstring_t get_child(xmlNode *node, const char *tag) { xmlNode *this_node; xmlNode *child; cstring_t buffer = cstring_alloc(); char *refid = NULL; char *declname = NULL; for (this_node = node->children; this_node; this_node = this_node->next) { if ((strcmp( (char*)this_node->name, "declname") == 0)) { declname = strdup((char*)this_node->children->content); } if ((this_node->type == XML_ELEMENT_NODE && this_node->children) && ((strcmp((char *)this_node->name, tag) == 0))) { refid = NULL; for (child = this_node->children; child; child = child->next) { if (child->content) { buffer = cstring_append_chars(buffer, (char *)child->content); } if ((strcmp( (char*)child->name, "ref") == 0)) { if (child->children->content) { buffer = cstring_append_chars(buffer, (char *)child->children->content); } refid = get_attr(child, "refid"); } } } if (declname && refid) { qb_map_put(used_structures_map, refid, declname); } } return buffer; } static struct param_info *find_param_by_name(struct qb_list_head *list, const char *name) { struct qb_list_head *iter; struct param_info *pi; qb_list_for_each(iter, list) { pi = qb_list_entry(iter, struct param_info, list); if (strcmp(pi->paramname, name) == 0) { return pi; } } return NULL; } static int not_all_whitespace(char *string) { unsigned int i; for (i=0; ichildren; this_tag; this_tag = this_tag->next) { for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) { if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameternamelist") == 0 && sub_tag->children->next->children) { paramname = (char*)sub_tag->children->next->children->content; } if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "parameterdescription") == 0 && paramname && sub_tag->children->next->children) { cstring_t paramdesc_c = get_text(sub_tag->children->next, NULL, NULL); paramdesc = cstring_to_chars(paramdesc_c); free(paramdesc_c); /* Add text to the param_map */ pi = find_param_by_name(list, paramname); if (pi) { pi->paramdesc = paramdesc; } else { pi = malloc(sizeof(struct param_info)); if (pi) { pi->paramname = paramname; pi->paramdesc = paramdesc; pi->paramtype = NULL; /* it's a retval */ qb_list_add_tail(&pi->list, list); } } } } } } static cstring_t get_codeline(xmlNode *this_tag) { cstring_t buffer = cstring_alloc(); xmlNode *sub_tag; for (sub_tag = this_tag; sub_tag; sub_tag = sub_tag->next) { if (strcmp((char*)sub_tag->name, "sp") == 0) { buffer = cstring_append_chars(buffer, " "); } if (strcmp((char*)sub_tag->name, "text") == 0) { // If the line starts with a dot then escape the first one to // stop nroff thinking it's a macro char *tmp = (char*)sub_tag->content; if (tmp[0] == '.') { buffer = cstring_append_chars(buffer, (char*)"\\[char46]"); tmp += 1; } buffer = cstring_append_chars(buffer, tmp); } if (strcmp((char*)sub_tag->name, "ref") == 0) { // Handled by the child recusion below } if (sub_tag->children) { char *tmp = get_codeline(sub_tag->children); buffer = cstring_append_cstring(buffer, tmp); cstring_free(tmp); } } return buffer; } static cstring_t get_codetree(xmlNode *cur_node) { xmlNode *this_tag; cstring_t buffer = cstring_alloc(); cstring_t tmp; if (print_man) { buffer = cstring_append_chars(buffer, "\n.nf\n"); } for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "codeline") == 0) { tmp = get_codeline(this_tag->children); buffer = cstring_append_cstring(buffer, tmp); cstring_free(tmp); } if (strcmp((char*)this_tag->name, "text") == 0) { buffer = cstring_append_chars(buffer, (char*)this_tag->content); } } if (print_man) { buffer = cstring_append_chars(buffer, ".fi\n"); } return buffer; } static cstring_t get_text(xmlNode *cur_node, char **returntext, char **notetext) { xmlNode *this_tag; xmlNode *sub_tag; char *kind; cstring_t buffer = cstring_alloc(); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_TEXT_NODE && strcmp((char *)this_tag->name, "text") == 0) { if (not_all_whitespace((char*)this_tag->content)) { buffer = cstring_append_chars(buffer, (char*)this_tag->content); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "emphasis") == 0) { if (print_man) { buffer = cstring_append_chars(buffer, "\\fB"); } buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); if (print_man) { buffer = cstring_append_chars(buffer, "\\fR"); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "ref") == 0) { if (print_man) { buffer = cstring_append_chars(buffer, "\\fI"); } buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); if (print_man) { buffer = cstring_append_chars(buffer, "\\fR"); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "computeroutput") == 0) { if (print_man) { buffer = cstring_append_chars(buffer, "\\fB"); } buffer = cstring_append_chars(buffer, (char*)this_tag->children->content); if (print_man) { buffer = cstring_append_chars(buffer, "\\fP"); } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "itemizedlist") == 0) { for (sub_tag = this_tag->children; sub_tag; sub_tag = sub_tag->next) { if (sub_tag->type == XML_ELEMENT_NODE && strcmp((char *)sub_tag->name, "listitem") == 0 && sub_tag->children->children->content) { buffer = cstring_append_chars(buffer, (char*)sub_tag->children->children->content); buffer = cstring_append_chars(buffer, "\n"); } } } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "programlisting") == 0) { cstring_t tmp = get_codetree(this_tag); buffer = cstring_append_cstring(buffer, tmp); buffer = cstring_append_chars(buffer, "\n"); cstring_free(tmp); } /* Look for subsections - return value & params */ if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "simplesect") == 0) { cstring_t tmp; kind = get_attr(this_tag, "kind"); tmp = get_text(this_tag->children, NULL, NULL); if (returntext && strcmp(kind, "return") == 0) { *returntext = cstring_to_chars(tmp); } if (notetext && strcmp(kind, "note") == 0) { *notetext = cstring_to_chars(tmp); } if (notetext && strcmp(kind, "par") == 0) { int type; tmp = get_child(this_tag, "title"); buffer = cstring_append_cstring(buffer, tmp); buffer = cstring_append_chars(buffer, "\n"); cstring_free(tmp); tmp = get_texttree(&type,this_tag, NULL, NULL, 1); buffer = cstring_append_cstring(buffer, tmp); buffer = cstring_append_chars(buffer, "\n"); } cstring_free(tmp); } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "parameterlist") == 0) { kind = get_attr(this_tag, "kind"); if (strcmp(kind, "param") == 0) { get_param_info(this_tag, ¶ms_list); } if (strcmp(kind, "retval") == 0) { get_param_info(this_tag, &retval_list); } } } return buffer; } static void read_structname(xmlNode *cur_node, void *arg) { struct struct_info *si=arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "compoundname") == 0) { si->structname = strdup((char*)this_tag->children->content); } } } static void read_structdesc(xmlNode *cur_node, void *arg) { struct struct_info *si=arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "detaileddescription") == 0) { cstring_t desc = get_texttree(NULL, this_tag, NULL, NULL, 1); si->description = cstring_to_chars(desc); cstring_free(desc); } if (strcmp((char*)this_tag->name, "briefdescription") == 0) { cstring_t brief = get_texttree(NULL, this_tag, NULL, NULL, 1); si->brief_description = cstring_to_chars(brief); } } } static void read_headername(xmlNode *cur_node, void *arg) { char **h_file = arg; xmlNode *this_tag; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "compoundname") == 0) { *h_file = strdup((char*)this_tag->children->content); } } } /* Called from traverse_node() */ static void read_struct(xmlNode *cur_node, void *arg) { xmlNode *this_tag; struct struct_info *si=arg; struct param_info *pi = NULL; char fullname[1024]; char *type = NULL; char *name = NULL; char *desc = NULL; const char *args=""; for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (strcmp((char*)this_tag->name, "type") == 0) { type = (char*)this_tag->children->content; /* If type is NULL then look for a ref - it's probably an external struct or typedef */ if (type == NULL) { cstring_t tmp = get_child(this_tag, "ref"); type = cstring_to_chars(tmp); cstring_free(tmp); } } if (strcmp((char*)this_tag->name, "name") == 0) { name = (char*)this_tag->children->content; } if (this_tag->children && strcmp((char*)this_tag->name, "argsstring") == 0) { args = (char*)this_tag->children->content; } if (this_tag->children && strcmp((char*)this_tag->name, "detaileddescription") == 0) { cstring_t *desc_cs = get_texttree(NULL, this_tag, NULL, NULL, 0); if (cstring_len(desc_cs) > 1) { desc = cstring_to_chars(desc_cs); } cstring_free(desc_cs); } } if (name) { pi = malloc(sizeof(struct param_info)); if (pi) { snprintf(fullname, sizeof(fullname), "%s%s", name, args); pi->paramtype = type?strdup(type):strdup(""); pi->paramname = strdup(fullname); pi->paramdesc = desc; qb_list_add_tail(&pi->list, &si->params_list); } } /* Tidy */ if (!name || !pi) { free(desc); } } static int read_structure_from_xml(const char *refid, const char *name) { char fname[PATH_MAX]; xmlNode *rootdoc; xmlDocPtr doc; struct struct_info *si; struct stat st; int ret = -1; snprintf(fname, sizeof(fname), "%s/%s.xml", xml_dir, refid); /* Don't call into libxml if the file does not exist - saves unwanted error messages */ if (stat(fname, &st) == -1) { return -1; } doc = xmlParseFile(fname); if (doc == NULL) { fprintf(stderr, "Error: unable to open xml file for %s\n", refid); return -1; } rootdoc = xmlDocGetRootElement(doc); if (!rootdoc) { fprintf(stderr, "Can't find \"document root\"\n"); return -1; } si = malloc(sizeof(struct struct_info)); if (si) { memset(si, 0, sizeof(*si)); si->kind = STRUCTINFO_STRUCT; qb_list_init(&si->params_list); traverse_node(rootdoc, "memberdef", read_struct, si); traverse_node(rootdoc, "compounddef", read_structdesc, si); traverse_node(rootdoc, "compounddef", read_structname, si); ret = 0; qb_map_put(structures_map, refid, si); } xmlFreeDoc(doc); return ret; } static char *allcaps(const char *name) { static char buffer[4096] = {'\0'}; size_t i; if (name) { size_t len = strnlen(name, 4096); for (i=0; i< len; i++) { buffer[i] = toupper(name[i]); } buffer[len] = '\0'; } return buffer; } /* * Print a structure comment that would be too long * to fit after the structure member, in a style ... * well, in a style like this! */ static void print_long_structure_comment(FILE *manfile, char *struct_comment) { int column = 7; char *comment = strdup(struct_comment); /* We're using strdup */ char *ptr = strtok(comment, " "); fprintf(manfile, "\\fP /*"); fprintf(manfile, "\n *"); while (ptr) { column += strlen(ptr)+1; if (column > 80) { fprintf(manfile, "\n *"); column = 7; } fprintf(manfile, " %s", ptr); ptr = strtok(NULL, " "); } fprintf(manfile, "\n */\n"); free(comment); } static void print_param(FILE *manfile, struct param_info *pi, int type_field_width, int name_field_width, int bold, const char *delimiter) { const char *asterisks = " "; char *type = pi->paramtype; int typelength = strlen(type); /* Reformat pointer params so they look nicer */ if (typelength > 0 && pi->paramtype[typelength-1] == '*') { asterisks=" *"; type = strdup(pi->paramtype); type[typelength-1] = '\0'; /* Cope with double pointers */ if (typelength > 1 && pi->paramtype[typelength-2] == '*') { asterisks="**"; type[typelength-2] = '\0'; } /* Tidy function pointers */ if (typelength > 1 && pi->paramtype[typelength-2] == '(') { asterisks="(*"; type[typelength-2] = '\0'; } } /* Print structure description if available */ if (pi->paramdesc) { /* Too long to go on the same line? */ if (strlen(pi->paramdesc) > STRUCT_COMMENT_LENGTH) { print_long_structure_comment(manfile, pi->paramdesc); fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n", bold?"\\fB":"", type_field_width, type, asterisks, bold?"\\fP":"", pi->paramname?pi->paramname:"", delimiter); } else { /* Pad out so they all line up */ int pad_length = (name_field_width+2) - (pi->paramname?strlen(pi->paramname):0) - strlen(delimiter) + 1; fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\\fR%*s/* %s*/\n", bold?"\\fB":"", type_field_width, type, asterisks, bold?"\\fP":"", pi->paramname?pi->paramname:"", delimiter, pad_length, " ", pi->paramdesc); } } else { fprintf(manfile, " %s%-*s%s%s\\fI%s\\fP%s\n", bold?"\\fB":"", type_field_width, type, asterisks, bold?"\\fP":"", pi->paramname?pi->paramname:"", delimiter); } if (type != pi->paramtype) { free(type); } } static void print_structure(FILE *manfile, struct struct_info *si) { struct param_info *pi; struct qb_list_head *iter; unsigned int max_param_length=0; unsigned int max_param_name_length=0; fprintf(manfile, ".nf\n"); if (si->brief_description) { fprintf(manfile, "%s\n", si->brief_description); } if (si->description) { fprintf(manfile, "%s\n", si->description); } qb_list_for_each(iter, &si->params_list) { pi = qb_list_entry(iter, struct param_info, list); if (strlen(pi->paramtype) > max_param_length) { max_param_length = strlen(pi->paramtype); } if (strlen(pi->paramname) > max_param_name_length) { max_param_name_length = strlen(pi->paramname); } } fprintf(manfile, "\\fB\n"); if (si->kind == STRUCTINFO_STRUCT) { fprintf(manfile, "struct %s {\n", si->structname); } else if (si->kind == STRUCTINFO_ENUM) { fprintf(manfile, "enum %s {\n", si->structname); } else { fprintf(manfile, "%s {\n", si->structname); } fprintf(manfile, "\\fR\n"); qb_list_for_each(iter, &si->params_list) { fprintf(manfile, "\\fB\n"); pi = qb_list_entry(iter, struct param_info, list); print_param(manfile, pi, max_param_length, max_param_name_length, 1, ";"); } fprintf(manfile, "};\n"); fprintf(manfile, "\\fP\n"); fprintf(manfile, ".fi\n"); } cstring_t get_texttree(int *type, xmlNode *cur_node, char **returntext, char **notetext, int add_nl) { xmlNode *this_tag; cstring_t tmp; cstring_t buffer = cstring_alloc(); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "para") == 0) { tmp = get_text(this_tag, returntext, notetext); buffer = cstring_append_cstring(buffer, tmp); if (add_nl) { buffer = cstring_append_chars(buffer, "\n"); } cstring_free(tmp); } } return buffer; } /* The text output is VERY basic and just a check that it's working really */ static void print_text(char *name, char *def, char *brief, char *args, char *detailed, struct qb_list_head *param_list, char *returntext, char *notetext) { printf(" ------------------ %s --------------------\n", name); printf("NAME\n"); if (brief) { printf(" %s - %s\n", name, brief); } else { printf(" %s\n", name); } printf("SYNOPSIS\n"); printf(" #include <%s%s>\n", header_prefix, headerfile); if (args) { printf(" %s %s\n\n", name, args); } if (detailed) { printf("DESCRIPTION\n"); printf(" %s\n", detailed); } if (returntext) { printf("RETURN VALUE\n"); printf(" %s\n", returntext); } if (notetext) { printf("NOTE\n"); printf(" %s\n", notetext); } } /* Print a long string with para marks in it. */ static void man_print_long_string(FILE *manfile, char *text) { char *next_nl; char *current = text; int in_prog = 0; next_nl = strchr(text, '\n'); while (next_nl && *next_nl != '\0') { *next_nl = '\0'; // Don't format @code blocks if (strncmp(current, ".nf", 3) == 0) { in_prog = 1; fprintf(manfile, "\n"); } if (in_prog) { fprintf(manfile, "%s\n", current); } else { if (strlen(current)) { fprintf(manfile, ".PP\n%s\n", current); } } if (strncmp(current, ".fi", 3) == 0) { in_prog = 0; fprintf(manfile, "\n"); } *next_nl = '\n'; current = next_nl+1; next_nl = strchr(current, '\n'); } /* The bit at the end */ if (strlen(current) && !in_prog) { fprintf(manfile, ".PP\n%s\n", current); } } static void print_manpage(char *name, char *def, char *brief, char *args, char *detailed, struct qb_list_head *param_map, char *returntext, char *notetext) { char manfilename[PATH_MAX]; char gendate[64]; const char *dateptr = gendate; FILE *manfile; time_t t; struct tm *tm; qb_map_iter_t *map_iter; struct qb_list_head *iter; struct qb_list_head *tmp; const char *p; void *data; unsigned int max_param_type_len; unsigned int max_param_name_len; unsigned int num_param_descs; int param_count = 0; int param_num = 0; struct param_info *pi; t = time(NULL); tm = localtime(&t); if (!tm) { perror("unable to get localtime"); exit(1); } strftime(gendate, sizeof(gendate), "%Y-%m-%d", tm); if (manpage_date) { dateptr = manpage_date; } if (manpage_year == LONG_MIN) { manpage_year = tm->tm_year+1900; } snprintf(manfilename, sizeof(manfilename), "%s/%s.%s", output_dir, name, man_section); manfile = fopen(manfilename, "w+"); if (!manfile) { perror("unable to open output file"); printf("%s", manfilename); exit(1); } /* Work out the length of the parameters, so we can line them up */ max_param_type_len = 0; max_param_name_len = 0; num_param_descs = 0; qb_list_for_each(iter, param_map) { pi = qb_list_entry(iter, struct param_info, list); /* It's mainly macros that break this, * macros need more work */ if (!pi->paramtype) { pi->paramtype = strdup(""); } if ((strlen(pi->paramtype) < LINE_LENGTH) && (strlen(pi->paramtype) > max_param_type_len)) { max_param_type_len = strlen(pi->paramtype); } if (strlen(pi->paramname) > max_param_name_len) { max_param_name_len = strlen(pi->paramname); } if (pi->paramdesc && pi->paramtype[0] != '\0') { num_param_descs++; } param_count++; } /* Off we go */ fprintf(manfile, ".\\\" Automatically generated man page, do not edit\n"); fprintf(manfile, ".TH %s %s %s \"%s\" \"%s\"\n", allcaps(name), man_section, dateptr, package_name, header); fprintf(manfile, ".SH NAME\n"); if (brief && not_all_whitespace(brief)) { fprintf(manfile, "%s \\- %s\n", name, brief); } else { fprintf(manfile, "%s\n", name); } fprintf(manfile, ".SH SYNOPSIS\n"); fprintf(manfile, ".nf\n"); fprintf(manfile, ".B #include <%s%s>\n", header_prefix, headerfile); if (def) { fprintf(manfile, ".sp\n"); fprintf(manfile, "\\fB%s\\fP(\n", def); qb_list_for_each(iter, param_map) { pi = qb_list_entry(iter, struct param_info, list); if (pi->paramtype[0] != '\0') { print_param(manfile, pi, max_param_type_len, max_param_name_len, 1, ++param_num < param_count?",":""); } } fprintf(manfile, ");\n"); fprintf(manfile, ".fi\n"); } if (print_params && num_param_descs) { fprintf(manfile, ".SH PARAMS\n"); qb_list_for_each(iter, ¶ms_list) { pi = qb_list_entry(iter, struct param_info, list); fprintf(manfile, "\\fB%-*s \\fP\\fI%s\\fP\n", (int)max_param_name_len, pi->paramname, pi->paramdesc); fprintf(manfile, ".PP\n"); } } if (detailed) { fprintf(manfile, ".SH DESCRIPTION\n"); man_print_long_string(manfile, detailed); } if (qb_map_count_get(used_structures_map)) { int first_struct = 1; map_iter = qb_map_iter_create(used_structures_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { struct struct_info *si; const char *refid = p; char *refname = data; /* If it's not been read in - go and look for it */ si = qb_map_get(structures_map, refid); if (!si) { if (!read_structure_from_xml(refid, refname)) { si = qb_map_get(structures_map, refid); } } /* Only print header if the struct files exist - sometimes they don't */ if (si && first_struct) { fprintf(manfile, ".SH STRUCTURES\n"); first_struct = 0; } if (si) { print_structure(manfile, si); fprintf(manfile, ".PP\n"); } } qb_map_iter_free(map_iter); fprintf(manfile, ".RE\n"); } if (returntext || !qb_list_empty(&retval_list)) { fprintf(manfile, ".SH RETURN VALUE\n"); if (returntext) { man_print_long_string(manfile, returntext); } fprintf(manfile, ".PP\n"); } qb_list_for_each(iter, &retval_list) { pi = qb_list_entry(iter, struct param_info, list); fprintf(manfile, "\\fB%-*s \\fP%s\n", 10, pi->paramname, pi->paramdesc); fprintf(manfile, ".PP\n"); } if (notetext) { fprintf(manfile, ".SH NOTE\n"); man_print_long_string(manfile, notetext); } fprintf(manfile, ".SH SEE ALSO\n"); fprintf(manfile, ".PP\n"); fprintf(manfile, ".nh\n"); fprintf(manfile, ".ad l\n"); param_num = 0; map_iter = qb_map_iter_create(function_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { /* Exclude us! */ if (strcmp(data, name)) { fprintf(manfile, "\\fI%s\\fR(%s)%s", (char *)data, man_section, param_num < (num_functions - 1)?", ":""); } param_num++; } qb_map_iter_free(map_iter); fprintf(manfile, "\n"); fprintf(manfile, ".ad\n"); fprintf(manfile, ".hy\n"); fprintf(manfile, ".SH \"COPYRIGHT\"\n"); fprintf(manfile, ".PP\n"); if (header_copyright[0] == 'C') { fprintf(manfile, "%s", header_copyright); /* String already contains trailing NL */ } else { fprintf(manfile, "Copyright (C) %4ld-%4ld %s, Inc. All rights reserved.\n", start_year, manpage_year, company); } fclose(manfile); /* Free the params & retval info */ qb_list_for_each_safe(iter, tmp, ¶ms_list) { pi = qb_list_entry(iter, struct param_info, list); qb_list_del(&pi->list); free_paraminfo(pi); } qb_list_for_each_safe(iter, tmp, &retval_list) { pi = qb_list_entry(iter, struct param_info, list); qb_list_del(&pi->list); free_paraminfo(pi); } /* Free used-structures map */ map_iter = qb_map_iter_create(used_structures_map); for (p = qb_map_iter_next(map_iter, &data); p; p = qb_map_iter_next(map_iter, &data)) { qb_map_rm(used_structures_map, p); free(data); } } /* Same as traverse_members, but to collect function names */ static void collect_functions(xmlNode *cur_node, void *arg) { xmlNode *this_tag; char *kind; char *name = NULL; if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) { kind = get_attr(cur_node, "kind"); if (kind && strcmp(kind, "function") == 0) { for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) { name = strdup((char *)this_tag->children->content); } } if (name) { qb_map_put(function_map, name, name); num_functions++; } } } } /* Same as traverse_members, but to collect enums. The behave like structures for, but, for some reason, are in the main XML file rather than their own */ static void collect_enums(xmlNode *cur_node, void *arg) { xmlNode *this_tag; struct struct_info *si; char *kind; char *refid = NULL; char *name = NULL; if (cur_node->name && strcmp((char *)cur_node->name, "memberdef") == 0) { kind = get_attr(cur_node, "kind"); if (kind && strcmp(kind, "enum") == 0) { refid = get_attr(cur_node, "id"); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) { name = strdup((char *)this_tag->children->content); } } if (name) { si = malloc(sizeof(struct struct_info)); if (si) { memset(si, 0, sizeof(*si)); si->kind = STRUCTINFO_ENUM; qb_list_init(&si->params_list); si->structname = strdup(name); traverse_node(cur_node, "enumvalue", read_struct, si); qb_map_put(structures_map, refid, si); } } } } } static void traverse_members(xmlNode *cur_node, void *arg) { xmlNode *this_tag; qb_list_init(¶ms_list); /* if arg == NULL then we're generating a page for the whole header file */ if ((cur_node->name && (strcmp((char *)cur_node->name, "memberdef") == 0)) || ((arg == NULL) && cur_node->name && strcmp((char *)cur_node->name, "compounddef")) == 0) { char *kind = NULL; char *def = NULL; char *args = NULL; char *name = NULL; char *brief = NULL; char *detailed = NULL; char *returntext = NULL; char *notetext = NULL; int type; kind=def=args=name=NULL; kind = get_attr(cur_node, "kind"); for (this_tag = cur_node->children; this_tag; this_tag = this_tag->next) { if (!this_tag->children || !this_tag->children->content) continue; if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "definition") == 0) def = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "argsstring") == 0) args = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "name") == 0) name = strdup((char *)this_tag->children->content); if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "briefdescription") == 0) { cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1); if (!brief) { brief = cstring_to_chars(tmp); } else { fprintf(stderr, "ERROR function %s has 2 briefdescription tags\n", name?name:"unknown"); } cstring_free(tmp); } if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "detaileddescription") == 0) { cstring_t tmp = get_texttree(&type, this_tag, &returntext, ¬etext, 1); if (!detailed) { detailed = cstring_to_chars(tmp); } else { fprintf(stderr, "ERROR function %s has 2 detaileddescription tags\n", name?name:"unknown"); } cstring_free(tmp); } /* Get all the params */ if (this_tag->type == XML_ELEMENT_NODE && strcmp((char *)this_tag->name, "param") == 0) { cstring_t param_type = get_child(this_tag, "type"); cstring_t param_name = get_child(this_tag, "declname"); struct param_info *pi = malloc(sizeof(struct param_info)); if (pi) { pi->paramname = cstring_to_chars(param_name); pi->paramtype = cstring_to_chars(param_type); pi->paramdesc = NULL; qb_list_add_tail(&pi->list, ¶ms_list); } } } if (arg == headerfile) { /* Print header page */ name = (char*)headerfile; if (print_man) { if (!quiet) { printf("Printing header manpage for %s\n", name); } print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } else { print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } } if (kind && strcmp(kind, "function") == 0) { /* Make sure function has a doxygen description */ if (!detailed) { fprintf(stderr, "No detailed description for function '%s' - please fix this\n", name); } if (!name) { fprintf(stderr, "Internal error - no name found for function\n"); } else { if (print_man) { if (!quiet) { printf("Printing manpage for %s\n", name); } print_manpage(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } else { print_text(name, def, brief, args, detailed, ¶ms_list, returntext, notetext); } } } free(kind); free(def); free(args); free(name); free(brief); free(detailed); } } static void traverse_node(xmlNode *parentnode, const char *leafname, void (do_members(xmlNode*, void*)), void *arg) { xmlNode *cur_node; for (cur_node = parentnode->children; cur_node; cur_node = cur_node->next) { if (cur_node->type == XML_ELEMENT_NODE && cur_node->name && strcmp((char*)cur_node->name, leafname)==0) { do_members(cur_node, arg); continue; } if (cur_node->type == XML_ELEMENT_NODE) { traverse_node(cur_node, leafname, do_members, arg); } } } static void usage(char *name) { printf("Usage:\n"); printf(" %s [OPTIONS] \n", name); printf("\n"); printf(" This is a tool to generate API manpages from a doxygen-annotated header file.\n"); printf(" First run doxygen on the file and then run this program against the main XML file\n"); printf(" it created and the directory containing the ancilliary files. It will then\n"); printf(" output a lot of *.3 man page files which you can then ship with your library.\n"); printf("\n"); printf(" You will need to invoke this program once for each .h file in your library,\n"); printf(" using the name of the generated .xml file. This file will usually be called\n"); printf(" something like _8h.xml, eg qbipcs_8h.xml\n"); printf("\n"); printf(" If you want HTML output then simply use nroff on the generated files as you\n"); printf(" would do with any other man page.\n"); printf("\n"); printf(" -a Print ASCII dump of man pages to stdout\n"); printf(" -m Write man page files to \n"); printf(" -P Print PARAMS section\n"); printf(" -g Print general man page for the whole header file\n"); printf(" -c Use the Copyright date from the header file (if one can be found)\n"); printf(" -O Directory for the original header file. Often needed by -c above\n"); printf(" -s Write man pages into section (default: 3)\n"); printf(" -p Use name (default: Package)\n"); printf(" -H
Set header (default: \"Programmer's Manual\"\n"); printf(" -I Set include filename (default taken from xml)\n"); printf(" -i Prefix for include files. eg qb/ (nothing by default)\n"); printf(" -C Company name in copyright (default: Red Hat)\n"); printf(" -D Date to print at top of man pages (format not checked, default: today)\n"); printf(" -S Start year to print at end of copyright line (default: 2010)\n"); printf(" -Y Year to print at end of copyright line (default: today's year)\n"); printf(" -o Write all man pages to (default: .)\n"); printf(" -d Directory for XML files (default: ./xml/)\n"); printf(" -h Print this usage text\n"); } static long get_year(char *optionarg, char optionchar) { long year = strtol(optionarg, NULL, 10); /* * Don't make too many assumptions about the year. I was on call at the * 2000 rollover. #experience */ if (year == LONG_MIN || year == LONG_MAX || year < 1900) { fprintf(stderr, "Value passed to -%c is not a valid year number\n", optionchar); return 0; } return year; } int main(int argc, char *argv[]) { xmlNode *rootdoc; xmlDocPtr doc; int opt; char xml_filename[PATH_MAX]; while ( (opt = getopt_long(argc, argv, "H:amqgcPD:Y:s:S:d:o:p:f:I:i:C:O:h?", NULL, NULL)) != EOF) { switch(opt) { case 'a': print_ascii = 1; print_man = 0; break; case 'm': print_man = 1; print_ascii = 0; break; case 'P': print_params = 1; break; case 'g': print_general = 1; break; case 'q': quiet = 1; break; case 'c': use_header_copyright = 1; break; case 'I': headerfile = optarg; break; case 'i': header_prefix = optarg; break; case 'C': company = optarg; break; case 's': man_section = optarg; break; case 'S': start_year = get_year(optarg, 'S'); if (start_year == 0) { return 1; } break; case 'd': xml_dir = optarg; break; case 'D': manpage_date = optarg; break; case 'Y': manpage_year = get_year(optarg, 'Y'); if (manpage_year == 0) { return 1; } break; case 'p': package_name = optarg; break; case 'H': header = optarg; break; case 'o': output_dir = optarg; break; case 'O': header_src_dir = optarg; break; case '?': case 'h': usage(argv[0]); return 0; } } if (argv[optind]) { xml_file = argv[optind]; } if (!xml_file) { usage(argv[0]); exit(1); } if (!quiet) { printf("reading %s ... ", xml_file); } snprintf(xml_filename, sizeof(xml_filename), "%s/%s", xml_dir, xml_file); doc = xmlParseFile(xml_filename); if (doc == NULL) { fprintf(stderr, "Error: unable to read xml file %s\n", xml_filename); exit(1); } rootdoc = xmlDocGetRootElement(doc); if (!rootdoc) { fprintf(stderr, "Can't find \"document root\"\n"); exit(1); } if (!quiet) printf("done.\n"); /* Get our header file name */ if (!headerfile) { traverse_node(rootdoc, "compounddef", read_headername, &headerfile); if (use_header_copyright) { /* And get the copyright line from this file if we can */ char file_path[PATH_MAX]; char file_line[256]; FILE *hfile; int lineno = 0; snprintf(file_path, sizeof(file_path), "%s/%s", header_src_dir, headerfile); hfile = fopen(file_path, "r"); if (hfile) { /* Don't look too far, this should be at the top */ while (!feof(hfile) && (lineno++ < 10)) { if (fgets(file_line, sizeof(file_line)-1, hfile)) { if (strncmp(file_line, " * Copyright", 12) == 0) { /* Keep the NL at the end of the buffer, it save us printing one */ strncpy(header_copyright, file_line+3, sizeof(header_copyright)-1); break; } } } fclose(hfile); } } } /* Default to *something* if it all goes wrong */ if (!headerfile) { headerfile = "unknown.h"; } qb_list_init(¶ms_list); qb_list_init(&retval_list); structures_map = qb_hashtable_create(10); function_map = qb_hashtable_create(10); used_structures_map = qb_hashtable_create(10); /* Collect functions */ traverse_node(rootdoc, "memberdef", collect_functions, NULL); /* Collect enums */ traverse_node(rootdoc, "memberdef", collect_enums, NULL); /* print pages */ traverse_node(rootdoc, "memberdef", traverse_members, NULL); if (print_general) { /* Generate and print a page for the headerfile itself */ traverse_node(rootdoc, "compounddef", traverse_members, (char *)headerfile); } return 0; } diff --git a/examples/ipcclient.c b/examples/ipcclient.c index 761eabb..5d6e90d 100644 --- a/examples/ipcclient.c +++ b/examples/ipcclient.c @@ -1,242 +1,242 @@ /* * Copyright (c) 2011 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "os_base.h" #include #include #include #include #include static int32_t do_benchmark = QB_FALSE; static int32_t use_events = QB_FALSE; static int alarm_notice; static qb_util_stopwatch_t *sw; #define ONE_MEG 1048576 static char *data; struct my_req { struct qb_ipc_request_header hdr; char message[256]; }; struct my_res { struct qb_ipc_response_header hdr; char message[256]; }; static void sigalrm_handler (int num) { alarm_notice = 1; } static void _benchmark(qb_ipcc_connection_t *conn, int write_size) { struct iovec iov[2]; ssize_t res; struct qb_ipc_request_header hdr; int write_count = 0; float secs; alarm_notice = 0; hdr.size = write_size; hdr.id = QB_IPC_MSG_USER_START + 1; - iov[0].iov_base = &hdr; + iov[0].iov_base = (void*)&hdr; iov[0].iov_len = sizeof(struct qb_ipc_request_header); iov[1].iov_base = data; iov[1].iov_len = write_size - sizeof(struct qb_ipc_request_header); alarm (10); qb_util_stopwatch_start(sw); do { res = qb_ipcc_sendv(conn, iov, 2); if (res == write_size) { write_count++; } } while (alarm_notice == 0 && (res == write_size || res == -EAGAIN)); if (res < 0) { perror("qb_ipcc_sendv"); } qb_util_stopwatch_stop(sw); secs = qb_util_stopwatch_sec_elapsed_get(sw); printf ("%5d messages sent ", write_count); printf ("%5d bytes per write ", write_size); printf ("%7.3f Seconds runtime ", secs); printf ("%9.3f TP/s ", ((float)write_count) / secs); printf ("%7.3f MB/s.\n", ((float)write_count) * ((float)write_size) / secs); } static void do_throughput_benchmark(qb_ipcc_connection_t *conn) { ssize_t size = 64; int i; signal (SIGALRM, sigalrm_handler); sw = qb_util_stopwatch_create(); for (i = 0; i < 10; i++) { /* number of repetitions - up to 50k */ _benchmark (conn, size); signal (SIGALRM, sigalrm_handler); size *= 5; if (size >= (ONE_MEG - 100)) { break; } } } static void do_echo(qb_ipcc_connection_t *conn) { struct my_req req; struct my_res res; char *newline; int32_t rc; int32_t send_ten_events; while (1) { printf("SEND (q or Q to quit) : "); if (fgets(req.message, 256, stdin) == NULL) { continue; } newline = strrchr(req.message, '\n'); if (newline) { *newline = '\0'; } if (strcasecmp(req.message, "q") == 0) { break; } else { req.hdr.id = QB_IPC_MSG_USER_START + 3; req.hdr.size = sizeof(struct my_req); rc = qb_ipcc_send(conn, &req, req.hdr.size); if (rc < 0) { perror("qb_ipcc_send"); exit(0); } } send_ten_events = (strcasecmp(req.message, "events") == 0); if (rc > 0) { if (use_events && !send_ten_events) { printf("waiting for event recv\n"); rc = qb_ipcc_event_recv(conn, &res, sizeof(res), -1); } else { printf("waiting for recv\n"); rc = qb_ipcc_recv(conn, &res, sizeof(res), -1); } printf("recv %d\n", rc); if (rc < 0) { perror("qb_ipcc_recv"); exit(0); } if (send_ten_events) { int32_t i; printf("waiting for 10 events\n"); for (i = 0; i < 10; i++) { rc = qb_ipcc_event_recv(conn, &res, sizeof(res), -1); if (rc < 0) { perror("qb_ipcc_event_recv"); } else { printf("got event %d rc:%d\n", i, rc); } } } printf("Response[%d]: %s \n", res.hdr.id, res.message); } } } static void show_usage(const char *name) { printf("usage: \n"); printf("%s \n", name); printf("\n"); printf(" options:\n"); printf("\n"); printf(" -h show this help text\n"); printf(" -b benchmark\n"); printf(" -e use events instead of responses\n"); printf("\n"); } int main(int argc, char *argv[]) { qb_ipcc_connection_t *conn; const char *options = "ebh"; int32_t opt; while ((opt = getopt(argc, argv, options)) != -1) { switch (opt) { case 'b': do_benchmark = QB_TRUE; break; case 'e': use_events = QB_TRUE; break; case 'h': default: show_usage(argv[0]); exit(0); break; } } qb_log_init("ipcclient", LOG_USER, LOG_TRACE); qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_format_set(QB_LOG_STDERR, "%f:%l [%p] %b"); qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); /* Our example server is enforcing a buffer size minimum, * so the client does not need to be concerned with setting * the buffer size */ conn = qb_ipcc_connect("ipcserver", 0); if (conn == NULL) { perror("qb_ipcc_connect"); exit(1); } data = calloc(1, qb_ipcc_get_buffer_size(conn)); if (do_benchmark) { do_throughput_benchmark(conn); } else { do_echo(conn); } qb_ipcc_disconnect(conn); free(data); qb_log_fini(); return EXIT_SUCCESS; } diff --git a/examples/ipcserver.c b/examples/ipcserver.c index d6828ff..d96bcf7 100644 --- a/examples/ipcserver.c +++ b/examples/ipcserver.c @@ -1,405 +1,405 @@ /* * Copyright (c) 2011 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "os_base.h" #include #include #include #include #include #include #include #ifdef HAVE_GLIB #include static GMainLoop *glib_loop; static qb_array_t *gio_map; #endif /* HAVE_GLIB */ #define ONE_MEG 1048576 static int32_t use_glib = QB_FALSE; static int32_t use_events = QB_FALSE; static qb_loop_t *bms_loop; static qb_ipcs_service_t *s1; static int32_t s1_connection_accept_fn(qb_ipcs_connection_t * c, uid_t uid, gid_t gid) { #if 0 if (uid == 0 && gid == 0) { qb_log(LOG_INFO, "Authenticated connection"); return 1; } qb_log(LOG_NOTICE, "BAD user!"); return 0; #else return 0; #endif } static void s1_connection_created_fn(qb_ipcs_connection_t * c) { struct qb_ipcs_stats srv_stats; qb_ipcs_stats_get(s1, &srv_stats, QB_FALSE); qb_log(LOG_INFO, "Connection created (active:%d, closed:%d)", srv_stats.active_connections, srv_stats.closed_connections); } static void s1_connection_destroyed_fn(qb_ipcs_connection_t * c) { qb_log(LOG_INFO, "Connection about to be freed"); } static int32_t s1_connection_closed_fn(qb_ipcs_connection_t * c) { struct qb_ipcs_connection_stats stats; struct qb_ipcs_stats srv_stats; qb_ipcs_stats_get(s1, &srv_stats, QB_FALSE); qb_ipcs_connection_stats_get(c, &stats, QB_FALSE); qb_log(LOG_INFO, "Connection to pid:%d destroyed (active:%d, closed:%d)", stats.client_pid, srv_stats.active_connections, srv_stats.closed_connections); qb_log(LOG_DEBUG, " Requests %"PRIu64"", stats.requests); qb_log(LOG_DEBUG, " Responses %"PRIu64"", stats.responses); qb_log(LOG_DEBUG, " Events %"PRIu64"", stats.events); qb_log(LOG_DEBUG, " Send retries %"PRIu64"", stats.send_retries); qb_log(LOG_DEBUG, " Recv retries %"PRIu64"", stats.recv_retries); qb_log(LOG_DEBUG, " FC state %d", stats.flow_control_state); qb_log(LOG_DEBUG, " FC count %"PRIu64"", stats.flow_control_count); return 0; } struct my_req { struct qb_ipc_request_header hdr; char message[256]; }; static int32_t s1_msg_process_fn(qb_ipcs_connection_t * c, void *data, size_t size) { struct qb_ipc_request_header *hdr; struct my_req *req_pt; struct qb_ipc_response_header response; ssize_t res; struct iovec iov[2]; char resp[100]; int32_t sl; int32_t send_ten_events = QB_FALSE; hdr = (struct qb_ipc_request_header *)data; if (hdr->id == (QB_IPC_MSG_USER_START + 1)) { return 0; } req_pt = (struct my_req *)data; qb_log(LOG_DEBUG, "msg received (id:%d, size:%d, data:%s)", req_pt->hdr.id, req_pt->hdr.size, req_pt->message); if (strcmp(req_pt->message, "kill") == 0) { exit(0); } response.size = sizeof(struct qb_ipc_response_header); response.id = 13; response.error = 0; sl = snprintf(resp, 100, "ACK %zu bytes", size) + 1; iov[0].iov_len = sizeof(response); - iov[0].iov_base = &response; + iov[0].iov_base = (void*)&response; iov[1].iov_len = sl; iov[1].iov_base = resp; response.size += sl; send_ten_events = (strcmp(req_pt->message, "events") == 0); if (use_events && !send_ten_events) { res = qb_ipcs_event_sendv(c, iov, 2); } else { res = qb_ipcs_response_sendv(c, iov, 2); } if (res < 0) { errno = - res; qb_perror(LOG_ERR, "qb_ipcs_response_send"); } if (send_ten_events) { int32_t i; qb_log(LOG_INFO, "request to send 10 events"); for (i = 0; i < 10; i++) { res = qb_ipcs_event_sendv(c, iov, 2); qb_log(LOG_INFO, "sent event %d res:%d", i, res); } } return 0; } static void sigusr1_handler(int32_t num) { qb_log(LOG_DEBUG, "(%d)", num); qb_ipcs_destroy(s1); exit(0); } static void show_usage(const char *name) { printf("usage: \n"); printf("%s \n", name); printf("\n"); printf(" options:\n"); printf("\n"); printf(" -h show this help text\n"); printf(" -m use shared memory\n"); printf(" -u use unix sockets\n"); printf(" -g use glib mainloop\n"); printf(" -e use events\n"); printf("\n"); } #ifdef HAVE_GLIB struct gio_to_qb_poll { int32_t is_used; int32_t events; int32_t source; int32_t fd; void *data; qb_ipcs_dispatch_fn_t fn; enum qb_loop_priority p; }; static gboolean gio_read_socket(GIOChannel * gio, GIOCondition condition, gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; gint fd = g_io_channel_unix_get_fd(gio); return (adaptor->fn(fd, condition, adaptor->data) == 0); } static void gio_poll_destroy(gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; adaptor->is_used--; if (adaptor->is_used == 0) { qb_log(LOG_DEBUG, "fd %d adaptor destroyed\n", adaptor->fd); adaptor->fd = 0; adaptor->source = 0; } } static int32_t my_g_dispatch_update(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn, gboolean is_new) { struct gio_to_qb_poll *adaptor; GIOChannel *channel; int32_t res = 0; res = qb_array_index(gio_map, fd, (void **)&adaptor); if (res < 0) { return res; } if (adaptor->is_used && adaptor->source) { if (is_new) { return -EEXIST; } g_source_remove(adaptor->source); adaptor->source = 0; } channel = g_io_channel_unix_new(fd); if (!channel) { return -ENOMEM; } adaptor->fn = fn; adaptor->events = evts; adaptor->data = data; adaptor->p = p; adaptor->is_used++; adaptor->fd = fd; adaptor->source = g_io_add_watch_full(channel, G_PRIORITY_DEFAULT, evts, gio_read_socket, adaptor, gio_poll_destroy); /* we are handing the channel off to be managed by mainloop now. * remove our reference. */ g_io_channel_unref(channel); return 0; } static int32_t my_g_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return my_g_dispatch_update(p, fd, evts, data, fn, TRUE); } static int32_t my_g_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return my_g_dispatch_update(p, fd, evts, data, fn, FALSE); } static int32_t my_g_dispatch_del(int32_t fd) { struct gio_to_qb_poll *adaptor; if (qb_array_index(gio_map, fd, (void **)&adaptor) == 0) { g_source_remove(adaptor->source); adaptor->source = 0; } return 0; } #endif /* HAVE_GLIB */ static int32_t my_job_add(enum qb_loop_priority p, void *data, qb_loop_job_dispatch_fn fn) { return qb_loop_job_add(bms_loop, p, data, fn); } static int32_t my_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return qb_loop_poll_add(bms_loop, p, fd, evts, data, fn); } static int32_t my_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return qb_loop_poll_mod(bms_loop, p, fd, evts, data, fn); } static int32_t my_dispatch_del(int32_t fd) { return qb_loop_poll_del(bms_loop, fd); } int32_t main(int32_t argc, char *argv[]) { const char *options = "mpseugh"; int32_t opt; int32_t rc; enum qb_ipc_type ipc_type = QB_IPC_NATIVE; struct qb_ipcs_service_handlers sh = { .connection_accept = s1_connection_accept_fn, .connection_created = s1_connection_created_fn, .msg_process = s1_msg_process_fn, .connection_destroyed = s1_connection_destroyed_fn, .connection_closed = s1_connection_closed_fn, }; struct qb_ipcs_poll_handlers ph = { .job_add = my_job_add, .dispatch_add = my_dispatch_add, .dispatch_mod = my_dispatch_mod, .dispatch_del = my_dispatch_del, }; #ifdef HAVE_GLIB struct qb_ipcs_poll_handlers glib_ph = { .job_add = NULL, /* FIXME */ .dispatch_add = my_g_dispatch_add, .dispatch_mod = my_g_dispatch_mod, .dispatch_del = my_g_dispatch_del, }; #endif /* HAVE_GLIB */ while ((opt = getopt(argc, argv, options)) != -1) { switch (opt) { case 'm': ipc_type = QB_IPC_SHM; break; case 'u': ipc_type = QB_IPC_SOCKET; break; case 'g': use_glib = QB_TRUE; break; case 'e': use_events = QB_TRUE; break; case 'h': default: show_usage(argv[0]); exit(0); break; } } signal(SIGINT, sigusr1_handler); qb_log_init("ipcserver", LOG_USER, LOG_TRACE); qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_format_set(QB_LOG_STDERR, "%f:%l [%p] %b"); qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); - s1 = qb_ipcs_create("ipcserver", 0, ipc_type, &sh); + s1 = qb_ipcs_create("123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890", 0, ipc_type, &sh); if (s1 == 0) { qb_perror(LOG_ERR, "qb_ipcs_create"); exit(1); } /* This forces the clients to use a minimum buffer size */ qb_ipcs_enforce_buffer_size(s1, ONE_MEG); if (!use_glib) { bms_loop = qb_loop_create(); qb_ipcs_poll_handlers_set(s1, &ph); rc = qb_ipcs_run(s1); if (rc != 0) { errno = -rc; qb_perror(LOG_ERR, "qb_ipcs_run"); exit(1); } qb_loop_run(bms_loop); } else { #ifdef HAVE_GLIB glib_loop = g_main_loop_new(NULL, FALSE); gio_map = qb_array_create_2(16, sizeof(struct gio_to_qb_poll), 1); qb_ipcs_poll_handlers_set(s1, &glib_ph); rc = qb_ipcs_run(s1); if (rc != 0) { errno = -rc; qb_perror(LOG_ERR, "qb_ipcs_run"); exit(1); } g_main_loop_run(glib_loop); #else qb_log(LOG_ERR, "You don't seem to have glib-devel installed.\n"); #endif } qb_log_fini(); return EXIT_SUCCESS; } diff --git a/lib/ipc_setup.c b/lib/ipc_setup.c index cec92f4..07c8466 100644 --- a/lib/ipc_setup.c +++ b/lib/ipc_setup.c @@ -1,978 +1,978 @@ /* * Copyright 2010-2024 Red Hat, Inc. * * Author: Angus Salkeld * * This file is part of libqb. * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "os_base.h" #include #if defined(HAVE_GETPEERUCRED) #include #endif #ifdef HAVE_SYS_UN_H #include #endif /* HAVE_SYS_UN_H */ #ifdef HAVE_SYS_STAT_H #include #endif #ifdef HAVE_SYS_MMAN_H #include #endif #include #include #include #include #include "util_int.h" #include "ipc_int.h" struct ipc_auth_ugp { uid_t uid; gid_t gid; pid_t pid; }; struct ipc_auth_data { int32_t sock; struct qb_ipcs_service *s; union { struct qb_ipc_connection_request req; struct qb_ipc_connection_response res; } msg; struct msghdr msg_recv; struct iovec iov_recv; struct ipc_auth_ugp ugp; size_t processed; size_t len; #ifdef SO_PASSCRED char *cmsg_cred; #endif }; static int32_t qb_ipcs_us_connection_acceptor(int fd, int revent, void *data); ssize_t qb_ipc_us_send(struct qb_ipc_one_way *one_way, const void *msg, size_t len) { int32_t result; int32_t processed = 0; char *rbuf = (char *)msg; qb_sigpipe_ctl(QB_SIGPIPE_IGNORE); retry_send: result = send(one_way->u.us.sock, &rbuf[processed], len - processed, MSG_NOSIGNAL); if (result == -1) { if (errno == EAGAIN && processed > 0) { goto retry_send; } else { qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); return -errno; } } processed += result; if (processed != len) { goto retry_send; } qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); return processed; } static ssize_t qb_ipc_us_recv_msghdr(struct ipc_auth_data *data) { char *msg = (char *) &data->msg; int32_t result; qb_sigpipe_ctl(QB_SIGPIPE_IGNORE); retry_recv: data->msg_recv.msg_iov->iov_base = &msg[data->processed]; data->msg_recv.msg_iov->iov_len = data->len - data->processed; result = recvmsg(data->sock, &data->msg_recv, MSG_NOSIGNAL | MSG_WAITALL); if (result == -1 && errno == EAGAIN) { qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); return -EAGAIN; } if (result == -1) { qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); return -errno; } if (result == 0) { qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); qb_util_log(LOG_DEBUG, "recv(fd %d) got 0 bytes assuming ENOTCONN", data->sock); return -ENOTCONN; } data->processed += result; if (data->processed != data->len) { goto retry_recv; } qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); assert(data->processed == data->len); return data->processed; } int32_t qb_ipc_us_sock_error_is_disconnected(int err) { if (err >= 0) { return QB_FALSE; } else if (err == -EAGAIN || err == -ETIMEDOUT || err == -EINTR || #ifdef EWOULDBLOCK err == -EWOULDBLOCK || #endif err == -EMSGSIZE || err == -ENOMSG || err == -EINVAL) { return QB_FALSE; } return QB_TRUE; } int32_t qb_ipc_us_ready(struct qb_ipc_one_way * ow_data, struct qb_ipc_one_way * ow_conn, int32_t ms_timeout, int32_t events) { struct pollfd ufds[2]; int32_t poll_events; int numfds = 1; int i; ufds[0].fd = ow_data->u.us.sock; ufds[0].events = events; ufds[0].revents = 0; if (ow_conn && ow_data != ow_conn) { numfds++; ufds[1].fd = ow_conn->u.us.sock; ufds[1].events = POLLIN; ufds[1].revents = 0; } poll_events = poll(ufds, numfds, ms_timeout); if ((poll_events == -1 && errno == EINTR) || poll_events == 0) { return -EAGAIN; } else if (poll_events == -1) { return -errno; } for (i = 0; i < poll_events; i++) { if (ufds[i].revents & POLLERR) { qb_util_log(LOG_DEBUG, "poll(fd %d) got POLLERR", ufds[i].fd); return -ENOTCONN; } else if (ufds[i].revents & POLLHUP) { qb_util_log(LOG_DEBUG, "poll(fd %d) got POLLHUP", ufds[i].fd); return -ENOTCONN; } else if (ufds[i].revents & POLLNVAL) { qb_util_log(LOG_DEBUG, "poll(fd %d) got POLLNVAL", ufds[i].fd); return -ENOTCONN; } else if (ufds[i].revents == 0) { qb_util_log(LOG_DEBUG, "poll(fd %d) zero revents", ufds[i].fd); return -ENOTCONN; } } return 0; } /* * recv an entire message - and try hard to get all of it. */ ssize_t qb_ipc_us_recv(struct qb_ipc_one_way * one_way, void *msg, size_t len, int32_t timeout) { int32_t result; int32_t final_rc = 0; int32_t processed = 0; int32_t to_recv = len; char *data = msg; qb_sigpipe_ctl(QB_SIGPIPE_IGNORE); retry_recv: result = recv(one_way->u.us.sock, &data[processed], to_recv, MSG_NOSIGNAL | MSG_WAITALL); if (result == -1) { if (errno == EAGAIN && (processed > 0 || timeout == -1)) { result = qb_ipc_us_ready(one_way, NULL, timeout, POLLIN); if (result == 0 || result == -EAGAIN) { goto retry_recv; } final_rc = result; goto cleanup_sigpipe; } else if (errno == ECONNRESET || errno == EPIPE) { final_rc = -ENOTCONN; goto cleanup_sigpipe; } else { final_rc = -errno; goto cleanup_sigpipe; } } if (result == 0) { final_rc = -ENOTCONN; goto cleanup_sigpipe; } processed += result; to_recv -= result; if (processed != len) { goto retry_recv; } final_rc = processed; cleanup_sigpipe: qb_sigpipe_ctl(QB_SIGPIPE_DEFAULT); return final_rc; } static int32_t qb_ipcc_stream_sock_connect(const char *socket_name, int32_t * sock_pt) { int32_t request_fd; struct sockaddr_un address; int32_t res = 0; request_fd = socket(PF_UNIX, SOCK_STREAM, 0); if (request_fd == -1) { return -errno; } qb_socket_nosigpipe(request_fd); res = qb_sys_fd_nonblock_cloexec_set(request_fd); if (res < 0) { goto error_connect; } memset(&address, 0, sizeof(struct sockaddr_un)); address.sun_family = AF_UNIX; #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN address.sun_len = QB_SUN_LEN(&address); #endif if (!use_filesystem_sockets()) { snprintf(address.sun_path + 1, UNIX_PATH_MAX - 1, "%s", socket_name); } else { snprintf(address.sun_path, sizeof(address.sun_path), "%s/%s", SOCKETDIR, socket_name); } if (connect(request_fd, (struct sockaddr *)&address, QB_SUN_LEN(&address)) == -1) { res = -errno; goto error_connect; } *sock_pt = request_fd; return 0; error_connect: close(request_fd); *sock_pt = -1; return res; } void qb_ipcc_us_sock_close(int32_t sock) { shutdown(sock, SHUT_RDWR); close(sock); } static int32_t qb_ipc_auth_creds(struct ipc_auth_data *data) { int32_t res = 0; /* * currently support getpeerucred, getpeereid, and SO_PASSCRED credential * retrieval mechanisms for various Platforms */ #ifdef HAVE_GETPEERUCRED /* * Solaris and some BSD systems */ { ucred_t *uc = NULL; if (getpeerucred(data->sock, &uc) == 0) { res = 0; data->ugp.uid = ucred_geteuid(uc); data->ugp.gid = ucred_getegid(uc); data->ugp.pid = ucred_getpid(uc); ucred_free(uc); } else { res = -errno; } } #elif defined(HAVE_GETPEEREID) /* * Usually MacOSX systems */ { /* * TODO get the peer's pid. * c->pid = ?; */ if (getpeereid(data->sock, &data->ugp.uid, &data->ugp.gid) == 0) { res = 0; } else { res = -errno; } } #elif defined(SO_PASSCRED) /* * Usually Linux systems */ { struct ucred cred; struct cmsghdr *cmsg; res = -EINVAL; for (cmsg = CMSG_FIRSTHDR(&data->msg_recv); cmsg != NULL; cmsg = CMSG_NXTHDR(&data->msg_recv, cmsg)) { if (cmsg->cmsg_type != SCM_CREDENTIALS) continue; memcpy(&cred, CMSG_DATA(cmsg), sizeof(struct ucred)); res = 0; data->ugp.pid = cred.pid; data->ugp.uid = cred.uid; data->ugp.gid = cred.gid; break; } } #else /* no credentials */ data->ugp.pid = 0; data->ugp.uid = 0; data->ugp.gid = 0; res = -ENOTSUP; #endif /* no credentials */ return res; } static void destroy_ipc_auth_data(struct ipc_auth_data *data) { if (data->s) { qb_ipcs_unref(data->s); } #ifdef SO_PASSCRED free(data->cmsg_cred); #endif free(data); } static struct ipc_auth_data * init_ipc_auth_data(int sock, size_t len) { struct ipc_auth_data *data = calloc(1, sizeof(struct ipc_auth_data)); if (data == NULL) { return NULL; } data->msg_recv.msg_iov = &data->iov_recv; data->msg_recv.msg_iovlen = 1; data->msg_recv.msg_name = 0; data->msg_recv.msg_namelen = 0; #ifdef SO_PASSCRED data->cmsg_cred = calloc(1, CMSG_SPACE(sizeof(struct ucred))); if (data->cmsg_cred == NULL) { destroy_ipc_auth_data(data); return NULL; } data->msg_recv.msg_control = (void *)data->cmsg_cred; data->msg_recv.msg_controllen = CMSG_SPACE(sizeof(struct ucred)); #endif #ifdef QB_SOLARIS data->msg_recv.msg_accrights = 0; data->msg_recv.msg_accrightslen = 0; #else data->msg_recv.msg_flags = 0; #endif /* QB_SOLARIS */ data->len = len; - data->iov_recv.iov_base = &data->msg; + data->iov_recv.iov_base = (void *)&data->msg; data->iov_recv.iov_len = data->len; data->sock = sock; return data; } int32_t qb_ipcc_us_setup_connect(struct qb_ipcc_connection *c, struct qb_ipc_connection_response *r) { int32_t res; struct qb_ipc_connection_request request; #ifdef QB_LINUX int on = 1; #endif res = qb_ipcc_stream_sock_connect(c->name, &c->setup.u.us.sock); if (res != 0) { return res; } #ifdef QB_LINUX res = setsockopt(c->setup.u.us.sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); if (res != 0) { int err = errno; qb_ipcc_us_sock_close(c->setup.u.us.sock); errno = err; return res; } #endif memset(&request, 0, sizeof(request)); request.hdr.id = QB_IPC_MSG_AUTHENTICATE; request.hdr.size = sizeof(request); request.max_msg_size = c->setup.max_msg_size; res = qb_ipc_us_send(&c->setup, &request, request.hdr.size); if (res < 0) { qb_ipcc_us_sock_close(c->setup.u.us.sock); return res; } /* ... To be continued ... (when the FD is active) */ return 0; } #define AUTH_RECV_MAX_RETRIES 10 #define AUTH_RECV_SLEEP_TIME_US 100 /* Called from ipcc_connect_continue() when async connect socket is active */ int qb_ipcc_setup_connect_continue(struct qb_ipcc_connection *c, struct qb_ipc_connection_response *r) { struct ipc_auth_data *data; int32_t res; int res1; int retry_count = 0; #ifdef QB_LINUX int off = 0; #endif data = init_ipc_auth_data(c->setup.u.us.sock, sizeof(struct qb_ipc_connection_response)); if (data == NULL) { qb_ipcc_us_sock_close(c->setup.u.us.sock); return -ENOMEM; } retry: res = qb_ipc_us_recv_msghdr(data); if (res == -EAGAIN && ++retry_count < AUTH_RECV_MAX_RETRIES) { struct timespec ts = {0, AUTH_RECV_SLEEP_TIME_US*QB_TIME_NS_IN_USEC}; struct timespec ts_left = {0, 0}; nanosleep(&ts, &ts_left); goto retry; } #ifdef QB_LINUX res1 = setsockopt(c->setup.u.us.sock, SOL_SOCKET, SO_PASSCRED, &off, sizeof(off)); if (res1 != 0) { int err = errno; destroy_ipc_auth_data(data); errno = err; return res; } #endif if (res != data->len) { destroy_ipc_auth_data(data); return res; } memcpy(r, &data->msg.res, sizeof(struct qb_ipc_connection_response)); qb_ipc_auth_creds(data); c->egid = data->ugp.gid; c->euid = data->ugp.uid; c->server_pid = data->ugp.pid; destroy_ipc_auth_data(data); return r->hdr.error; } /* ************************************************************************** * SERVER */ int32_t qb_ipcs_us_publish(struct qb_ipcs_service * s) { struct sockaddr_un un_addr; int32_t res; #ifdef SO_PASSCRED int on = 1; #endif /* * Create socket for IPC clients, name socket, listen for connections */ s->server_sock = socket(PF_UNIX, SOCK_STREAM, 0); if (s->server_sock == -1) { res = -errno; qb_util_perror(LOG_ERR, "Cannot create server socket"); return res; } res = qb_sys_fd_nonblock_cloexec_set(s->server_sock); if (res < 0) { goto error_close; } memset(&un_addr, 0, sizeof(struct sockaddr_un)); un_addr.sun_family = AF_UNIX; #if defined(QB_BSD) || defined(QB_DARWIN) un_addr.sun_len = SUN_LEN(&un_addr); #endif qb_util_log(LOG_INFO, "server name: %s", s->name); if (!use_filesystem_sockets()) { snprintf(un_addr.sun_path + 1, UNIX_PATH_MAX - 1, "%s", s->name); } else { struct stat stat_out; res = stat(SOCKETDIR, &stat_out); if (res == -1 || (res == 0 && !S_ISDIR(stat_out.st_mode))) { res = -errno; qb_util_log(LOG_CRIT, "Required directory not present %s", SOCKETDIR); goto error_close; } snprintf(un_addr.sun_path, sizeof(un_addr.sun_path), "%s/%s", SOCKETDIR, s->name); unlink(un_addr.sun_path); } res = bind(s->server_sock, (struct sockaddr *)&un_addr, QB_SUN_LEN(&un_addr)); if (res) { res = -errno; qb_util_perror(LOG_ERR, "Could not bind AF_UNIX (%s)", un_addr.sun_path); goto error_close; } /* * Allow everyone to write to the socket since the IPC layer handles * security automatically */ if (use_filesystem_sockets()) { (void)chmod(un_addr.sun_path, S_IRWXU | S_IRWXG | S_IRWXO); } #ifdef SO_PASSCRED (void)setsockopt(s->server_sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); #endif if (listen(s->server_sock, SERVER_BACKLOG) == -1) { qb_util_perror(LOG_ERR, "socket listen failed"); } res = s->poll_fns.dispatch_add(s->poll_priority, s->server_sock, POLLIN | POLLPRI | POLLNVAL, s, qb_ipcs_us_connection_acceptor); return res; error_close: close(s->server_sock); return res; } int32_t qb_ipcs_us_withdraw(struct qb_ipcs_service * s) { qb_util_log(LOG_INFO, "withdrawing server sockets"); (void)s->poll_fns.dispatch_del(s->server_sock); shutdown(s->server_sock, SHUT_RDWR); if (use_filesystem_sockets()) { struct sockaddr_un sockname; socklen_t socklen = sizeof(sockname); if ((getsockname(s->server_sock, (struct sockaddr *)&sockname, &socklen) == 0) && sockname.sun_family == AF_UNIX) { #ifdef HAVE_STRUCT_SOCKADDR_UN_SUN_LEN /* * Terminating NUL on FreeBSD is not part of the sun_path. * Add it to use sun_path as a parameter of unlink */ sockname.sun_path[sockname.sun_len - offsetof(struct sockaddr_un, sun_path)] = '\0'; #endif unlink(sockname.sun_path); } } close(s->server_sock); s->server_sock = -1; return 0; } static int32_t handle_new_connection(struct qb_ipcs_service *s, int32_t auth_result, int32_t sock, void *msg, size_t len, struct ipc_auth_ugp *ugp) { struct qb_ipcs_connection *c = NULL; struct qb_ipc_connection_request *req = msg; int32_t res = auth_result; int32_t res2 = 0; uint32_t max_buffer_size = QB_MAX(req->max_msg_size, s->max_buffer_size); struct qb_ipc_connection_response response; const char suffix[] = "/qb"; int desc_len; c = qb_ipcs_connection_alloc(s); if (c == NULL) { qb_ipcc_us_sock_close(sock); return -ENOMEM; } c->receive_buf = calloc(1, max_buffer_size); if (c->receive_buf == NULL) { free(c); qb_ipcc_us_sock_close(sock); return -ENOMEM; } c->setup.u.us.sock = sock; c->request.max_msg_size = max_buffer_size; c->response.max_msg_size = max_buffer_size; c->event.max_msg_size = max_buffer_size; c->pid = ugp->pid; c->auth.uid = c->euid = ugp->uid; c->auth.gid = c->egid = ugp->gid; c->auth.mode = 0600; c->stats.client_pid = ugp->pid; memset(&response, 0, sizeof(response)); #if defined(QB_LINUX) || defined(QB_CYGWIN) desc_len = snprintf(c->description, CONNECTION_DESCRIPTION - sizeof suffix, "/dev/shm/qb-%d-%d-%d-XXXXXX", s->pid, ugp->pid, c->setup.u.us.sock); if (desc_len < 0) { res = -errno; goto send_response; } if (desc_len >= CONNECTION_DESCRIPTION - sizeof suffix) { res = -ENAMETOOLONG; goto send_response; } if (mkdtemp(c->description) == NULL) { res = -errno; goto send_response; } if (chmod(c->description, 0770)) { res = -errno; goto send_response; } /* chown can fail because we might not be root */ (void)chown(c->description, c->auth.uid, c->auth.gid); /* We can't pass just a directory spec to the clients */ memcpy(c->description + desc_len, suffix, sizeof suffix); #else desc_len = snprintf(c->description, CONNECTION_DESCRIPTION, "%d-%d-%d", s->pid, ugp->pid, c->setup.u.us.sock); if (desc_len < 0) { res = -errno; goto send_response; } if (desc_len >= CONNECTION_DESCRIPTION) { res = -ENAMETOOLONG; goto send_response; } #endif if (auth_result == 0 && c->service->serv_fns.connection_accept) { res = c->service->serv_fns.connection_accept(c, c->euid, c->egid); } if (res != 0) { goto send_response; } qb_util_log(LOG_DEBUG, "IPC credentials authenticated (%s)", c->description); if (s->funcs.connect) { res = s->funcs.connect(s, c, &response); if (res != 0) { goto send_response; } } /* * The connection is good, add it to the active connection list */ c->state = QB_IPCS_CONNECTION_ACTIVE; qb_list_add(&c->list, &s->connections); send_response: response.hdr.id = QB_IPC_MSG_AUTHENTICATE; response.hdr.size = sizeof(response); response.hdr.error = res; if (res == 0) { response.connection = (intptr_t) c; response.connection_type = s->type; response.max_msg_size = c->request.max_msg_size; s->stats.active_connections++; } res2 = qb_ipc_us_send(&c->setup, &response, response.hdr.size); if (res == 0 && res2 != response.hdr.size) { res = res2; } if (res == 0) { qb_ipcs_connection_ref(c); if (s->serv_fns.connection_created) { s->serv_fns.connection_created(c); } if (c->state == QB_IPCS_CONNECTION_ACTIVE) { c->state = QB_IPCS_CONNECTION_ESTABLISHED; } qb_ipcs_connection_unref(c); } else { if (res == -EACCES) { qb_util_log(LOG_INFO, "IPC connection credentials rejected (%s)", c->description); } else if (res == -EAGAIN) { qb_util_log(LOG_INFO, "IPC connection not ready (%s)", c->description); } else { qb_util_perror(LOG_INFO, "IPC connection setup failed (%s)", c->description); errno = -res; } if (c->state == QB_IPCS_CONNECTION_INACTIVE) { /* This removes the initial alloc ref */ qb_ipcs_connection_unref(c); qb_ipcc_us_sock_close(sock); } else { qb_ipcs_disconnect(c); } } return res; } static int32_t process_auth(int32_t fd, int32_t revents, void *d) { struct ipc_auth_data *data = (struct ipc_auth_data *) d; int32_t res = 0; int res1; #ifdef SO_PASSCRED int off = 0; #endif if (data->s->server_sock == -1) { qb_util_log(LOG_DEBUG, "Closing fd (%d) for server shutdown", fd); res = -ESHUTDOWN; goto cleanup_and_return; } if (revents & POLLNVAL) { qb_util_log(LOG_DEBUG, "NVAL conn fd (%d)", fd); res = -EINVAL; goto cleanup_and_return; } if (revents & POLLHUP) { qb_util_log(LOG_DEBUG, "HUP conn fd (%d)", fd); res = -ESHUTDOWN; goto cleanup_and_return; } if ((revents & POLLIN) == 0) { return 0; } res = qb_ipc_us_recv_msghdr(data); if (res == -EAGAIN) { /* yield to mainloop, Let mainloop call us again */ return 0; } if (res != data->len) { res = -EIO; goto cleanup_and_return; } res = qb_ipc_auth_creds(data); cleanup_and_return: #ifdef SO_PASSCRED res1 = setsockopt(data->sock, SOL_SOCKET, SO_PASSCRED, &off, sizeof(off)); if (res1 != 0) { int err = errno; close(data->sock); errno = err; return res; } #endif (void)data->s->poll_fns.dispatch_del(data->sock); if (res < 0) { close(data->sock); } else if (data->msg.req.hdr.id == QB_IPC_MSG_AUTHENTICATE) { (void)handle_new_connection(data->s, res, data->sock, &data->msg, data->len, &data->ugp); } else { close(data->sock); } destroy_ipc_auth_data(data); return 1; } static void qb_ipcs_uc_recv_and_auth(int32_t sock, struct qb_ipcs_service *s) { int res = 0; int res1; struct ipc_auth_data *data = NULL; #ifdef SO_PASSCRED int on = 1; #endif data = init_ipc_auth_data(sock, sizeof(struct qb_ipc_connection_request)); if (data == NULL) { close(sock); /* -ENOMEM */ return; } data->s = s; qb_ipcs_ref(data->s); #ifdef SO_PASSCRED res1 = setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on)); if (res1 != 0) { close(sock); return; } #endif res = s->poll_fns.dispatch_add(s->poll_priority, data->sock, POLLIN | POLLPRI | POLLNVAL, data, process_auth); if (res < 0) { qb_util_log(LOG_DEBUG, "Failed to arrange for AUTH for fd (%d)", data->sock); close(sock); destroy_ipc_auth_data(data); } } static int32_t qb_ipcs_us_connection_acceptor(int fd, int revent, void *data) { struct sockaddr_un un_addr; int32_t new_fd; struct qb_ipcs_service *s = (struct qb_ipcs_service *)data; int32_t res; socklen_t addrlen = sizeof(struct sockaddr_un); if (revent & (POLLNVAL | POLLHUP | POLLERR)) { /* * handle shutdown more cleanly. */ return -1; } retry_accept: errno = 0; new_fd = accept(fd, (struct sockaddr *)&un_addr, &addrlen); if (new_fd == -1 && errno == EINTR) { goto retry_accept; } if (new_fd == -1 && errno == EBADF) { qb_util_perror(LOG_ERR, "Could not accept client connection from fd:%d", fd); return -1; } if (new_fd == -1) { qb_util_perror(LOG_ERR, "Could not accept client connection"); /* This is an error, but -1 would indicate disconnect * from the poll loop */ return 0; } res = qb_sys_fd_nonblock_cloexec_set(new_fd); if (res < 0) { close(new_fd); /* This is an error, but -1 would indicate disconnect * from the poll loop */ return 0; } qb_ipcs_uc_recv_and_auth(new_fd, s); return 0; } void remove_tempdir(const char *name) { #if defined(QB_LINUX) || defined(QB_CYGWIN) char dirname[PATH_MAX]; char *slash = strrchr(name, '/'); if (slash && slash - name < sizeof dirname) { memcpy(dirname, name, slash - name); dirname[slash - name] = '\0'; /* This gets called more than it needs to be really, so we don't check * the return code. It's more of a desperate attempt to clean up after ourself * in either the server or client. */ (void)rmdir(dirname); } #endif } diff --git a/lib/unix.c b/lib/unix.c index 6bd3cc2..dfbf53e 100644 --- a/lib/unix.c +++ b/lib/unix.c @@ -1,617 +1,617 @@ /* * Copyright (C) 2011 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "os_base.h" #ifdef HAVE_SYS_SHM_H #include #endif #ifdef HAVE_SYS_MMAN_H #include #endif #if defined(HAVE_FCNTL_H) && defined(HAVE_POSIX_FALLOCATE) #include #endif #include "util_int.h" #include #include #include #if defined(MAP_ANON) && ! defined(MAP_ANONYMOUS) /* * BSD derivatives usually have MAP_ANON, not MAP_ANONYMOUS **/ #define MAP_ANONYMOUS MAP_ANON #endif char * qb_strerror_r(int errnum, char *buf, size_t buflen) { #ifdef STRERROR_R_CHAR_P return strerror_r(errnum, buf, buflen); #else if (strerror_r(errnum, buf, buflen) != 0) { buf[0] = '\0'; } return buf; #endif /* STRERROR_R_CHAR_P */ } static int32_t open_mmap_file(char *path, uint32_t file_flags) { if (strstr(path, "XXXXXX") != NULL) { mode_t old_mode = umask(077); int32_t temp_fd = mkstemp(path); (void)umask(old_mode); return temp_fd; } return open(path, file_flags, 0600); } -#if defined(QB_BSD) || !defined(HAVE_POSIX_FALLOCATE) +#if defined(QB_BSD) || defined(QB_SOLARIS) || !defined(HAVE_POSIX_FALLOCATE) static int local_fallocate(int fd, size_t bytes) { long page_size = sysconf(_SC_PAGESIZE); long write_size = QB_MIN(page_size, bytes); char *buffer = NULL; int i; size_t written; if (page_size < 0) { goto error_exit; } buffer = calloc(1, write_size); if (buffer == NULL) { goto error_exit; } for (i = 0; i < (bytes / write_size); i++) { retry_write: written = write(fd, buffer, write_size); if (written == -1 && errno == EINTR) { goto retry_write; } if (written != write_size) { free(buffer); errno = ENOSPC; goto error_exit; } } free(buffer); return 0; error_exit: return -1; } #endif int32_t qb_sys_mmap_file_open(char *path, const char *file, size_t bytes, uint32_t file_flags) { int32_t fd; int32_t res = 0; #ifndef HAVE_POSIX_FALLOCATE ssize_t written; char *buffer = NULL; int32_t i; #endif char *is_absolute = strchr(file, '/'); #ifdef HAVE_POSIX_FALLOCATE int32_t fallocate_retry = 5; #endif if (is_absolute) { (void)strlcpy(path, file, PATH_MAX); } else { #if defined(QB_LINUX) || defined(QB_CYGWIN) /* This is only now called when talking to an old libqb where we need to add qb- to the name */ snprintf(path, PATH_MAX, "/dev/shm/qb-%s", file); #else snprintf(path, PATH_MAX, "%s/%s", SOCKETDIR, file); is_absolute = path; #endif } fd = open_mmap_file(path, file_flags); if (fd < 0 && !is_absolute) { qb_util_perror(LOG_ERR, "couldn't open file %s", path); snprintf(path, PATH_MAX, "%s/%s", SOCKETDIR, file); fd = open_mmap_file(path, file_flags); if (fd < 0) { res = -errno; qb_util_perror(LOG_ERR, "couldn't open file %s", path); return res; } } else if (fd < 0 && is_absolute) { res = -errno; qb_util_perror(LOG_ERR, "couldn't open file %s", path); return res; } #ifndef _WIN32 /* ftruncate not supported on WSL https://github.com/microsoft/WSL/issues/902 */ if (ftruncate(fd, bytes) == -1) { res = -errno; qb_util_perror(LOG_ERR, "couldn't truncate file %s", path); goto unlink_exit; } #endif #ifdef HAVE_POSIX_FALLOCATE /* posix_fallocate(3) can be interrupted by a signal, so retry few times before giving up */ do { fallocate_retry--; res = posix_fallocate(fd, 0, bytes); if (res == EINTR) { qb_util_log(LOG_DEBUG, "got EINTR trying to allocate file %s, retrying...", path); continue; -#ifdef QB_BSD +#if defined (QB_BSD) || defined(QB_SOLARIS) } else if (res == EINVAL) { /* posix_fallocate() fails on ZFS https://lists.freebsd.org/pipermail/freebsd-current/2018-February/068448.html */ qb_util_log(LOG_DEBUG, "posix_fallocate returned EINVAL - running on ZFS?"); if (file_flags & O_CREAT) { if (local_fallocate(fd, bytes)) { goto unlink_exit; } } #endif } else if (res != 0) { errno = res; res = -1 * res; qb_util_perror(LOG_ERR, "couldn't allocate file %s", path); goto unlink_exit; } break; } while (fallocate_retry > 0); #else if (file_flags & O_CREAT) { if (local_fallocate(fd, bytes)) { goto unlink_exit; } } #endif /* HAVE_POSIX_FALLOCATE */ return fd; unlink_exit: unlink(path); if (fd >= 0) { close(fd); } return res; } int32_t qb_sys_circular_mmap(int32_t fd, void **buf, size_t bytes) { void *addr_orig = NULL; void *addr; void *addr_next; int32_t res; int flags = MAP_ANONYMOUS; #ifdef QB_FORCE_SHM_ALIGN /* On a number of arches any fixed and shared mmap() mapping address * must be aligned to 16k. If the first mmap() below is not shared then * the first mmap() will succeed because these restrictions do not apply to * private mappings. The second mmap() wants a shared memory mapping but * the address returned by the first one is only page-aligned and not * aligned to 16k. */ flags |= MAP_SHARED; #else flags |= MAP_PRIVATE; #endif /* QB_FORCE_SHM_ALIGN */ #if defined(QB_ARCH_HPPA) /* map twice the size we want to make sure we have already mapped the second memory location behind it too. Otherwise the Linux kernel may map it in the upper memory so that we can't map the second part afterwards since it will conflict. */ addr = mmap(NULL, 2*bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (addr == MAP_FAILED) return -errno; addr_orig = addr; #else addr_orig = mmap(NULL, bytes << 1, PROT_NONE, flags, -1, 0); if (addr_orig == MAP_FAILED) { return -errno; } addr = mmap(addr_orig, bytes, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0); #endif if (addr != addr_orig) { res = -errno; goto cleanup_fail; } #if defined(QB_BSD) && defined(MADV_NOSYNC) madvise(addr_orig, bytes, MADV_NOSYNC); #endif addr_next = ((char *)addr_orig) + bytes; addr = mmap(addr_next, bytes, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, fd, 0); if (addr != addr_next) { res = -errno; goto cleanup_fail; } #if defined(QB_BSD) && defined(MADV_NOSYNC) madvise(((char *)addr_orig) + bytes, bytes, MADV_NOSYNC); #endif res = close(fd); if (res) { goto cleanup_fail_noclose; } *buf = addr_orig; return 0; cleanup_fail: close(fd); cleanup_fail_noclose: if (addr_orig) { munmap(addr_orig, bytes << 1); } return res; } int32_t qb_sys_fd_nonblock_cloexec_set(int32_t fd) { int32_t res = 0; int32_t oldflags = fcntl(fd, F_GETFD, 0); if (oldflags < 0) { oldflags = 0; } oldflags |= FD_CLOEXEC; res = fcntl(fd, F_SETFD, oldflags); if (res == -1) { res = -errno; qb_util_perror(LOG_ERR, "Could not set close-on-exit on fd:%d", fd); return res; } res = fcntl(fd, F_SETFL, O_NONBLOCK); if (res == -1) { res = -errno; qb_util_log(LOG_ERR, "Could not set non-blocking on fd:%d", fd); } return res; } int32_t qb_sys_unlink_or_truncate(const char *path, int32_t truncate_fallback) { int32_t res = 0; if (unlink(path) == -1) { res = errno; qb_util_perror(LOG_DEBUG, "Unlinking file: %s", path); if (res != ENOENT && truncate_fallback) { res = errno = 0; if (truncate(path, 0) == -1) { res = errno; qb_util_perror(LOG_DEBUG, "Truncating file: %s", path); } } } return -res; } #if defined(HAVE_OPENAT) && defined(HAVE_UNLINKAT) int32_t qb_sys_unlink_or_truncate_at(int32_t dirfd, const char *path, int32_t truncate_fallback) { int32_t fd, res = 0; if (unlinkat(dirfd, path, 0) == -1) { res = errno; qb_util_perror(LOG_DEBUG, "Unlinking file at dir: %s", path); if (res != ENOENT && truncate_fallback) { res = errno = 0; if ((fd = openat(dirfd, path, O_WRONLY|O_TRUNC)) == -1) { res = errno; qb_util_perror(LOG_DEBUG, "Truncating file at dir: %s", path); } else { close(fd); } } } return -res; } #endif void qb_sigpipe_ctl(enum qb_sigpipe_ctl ctl) { #if !defined(HAVE_MSG_NOSIGNAL) && !defined(HAVE_SO_NOSIGPIPE) struct sigaction act; struct sigaction oact; act.sa_handler = SIG_IGN; if (ctl == QB_SIGPIPE_IGNORE) { sigaction(SIGPIPE, &act, &oact); } else { sigaction(SIGPIPE, &oact, NULL); } #endif /* !MSG_NOSIGNAL && !SO_NOSIGPIPE */ } void qb_socket_nosigpipe(int32_t s) { #if !defined(HAVE_MSG_NOSIGNAL) && defined(HAVE_SO_NOSIGPIPE) int32_t on = 1; setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, (void *)&on, sizeof(on)); #endif /* !MSG_NOSIGNAL && SO_NOSIGPIPE */ } /* * atomic operations * -------------------------------------------------------------------------- */ #ifndef HAVE_GCC_BUILTINS_FOR_SYNC_OPERATIONS /* * We have to use the slow, but safe locking method */ static qb_thread_lock_t *qb_atomic_mutex = NULL; void qb_atomic_init(void) { if (qb_atomic_mutex == NULL) { qb_atomic_mutex = qb_thread_lock_create(QB_THREAD_LOCK_SHORT); } assert(qb_atomic_mutex); } int32_t qb_atomic_int_exchange_and_add(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t val) { int32_t result; qb_thread_lock(qb_atomic_mutex); result = *atomic; *atomic += val; qb_thread_unlock(qb_atomic_mutex); return result; } void qb_atomic_int_add(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t val) { qb_thread_lock(qb_atomic_mutex); *atomic += val; qb_thread_unlock(qb_atomic_mutex); } int32_t qb_atomic_int_compare_and_exchange(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t oldval, int32_t newval) { int32_t result; qb_thread_lock(qb_atomic_mutex); if (*atomic == oldval) { result = QB_TRUE; *atomic = newval; } else { result = QB_FALSE; } qb_thread_unlock(qb_atomic_mutex); return result; } int32_t qb_atomic_pointer_compare_and_exchange(volatile void *QB_GNUC_MAY_ALIAS * atomic, void *oldval, void *newval) { int32_t result; qb_thread_lock(qb_atomic_mutex); if (*atomic == oldval) { result = QB_TRUE; *atomic = newval; } else { result = QB_FALSE; } qb_thread_unlock(qb_atomic_mutex); return result; } #ifdef QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED int32_t (qb_atomic_int_get) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic) { int32_t result; qb_thread_lock(qb_atomic_mutex); result = *atomic; qb_thread_unlock(qb_atomic_mutex); return result; } void (qb_atomic_int_set) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t newval) { qb_thread_lock(qb_atomic_mutex); *atomic = newval; qb_thread_unlock(qb_atomic_mutex); } void * (qb_atomic_pointer_get) (volatile void *QB_GNUC_MAY_ALIAS * atomic) { void *result; qb_thread_lock(qb_atomic_mutex); result = (void*)*atomic; qb_thread_unlock(qb_atomic_mutex); return result; } void (qb_atomic_pointer_set) (volatile void *QB_GNUC_MAY_ALIAS * atomic, void *newval) { qb_thread_lock(qb_atomic_mutex); *atomic = newval; qb_thread_unlock(qb_atomic_mutex); } #endif /* QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED */ #else /* * gcc built-ins */ void qb_atomic_init(void) { } int32_t qb_atomic_int_exchange_and_add(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t val) { return __sync_fetch_and_add(atomic, val); } void qb_atomic_int_add(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t val) { __sync_fetch_and_add(atomic, val); } int32_t qb_atomic_int_compare_and_exchange(volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t oldval, int32_t newval) { return __sync_bool_compare_and_swap(atomic, oldval, newval); } int32_t qb_atomic_pointer_compare_and_exchange(volatile void *QB_GNUC_MAY_ALIAS * atomic, void *oldval, void *newval) { return __sync_bool_compare_and_swap(atomic, oldval, newval); } #ifdef QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED #define QB_ATOMIC_MEMORY_BARRIER __sync_synchronize () int32_t (qb_atomic_int_get) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic) { QB_ATOMIC_MEMORY_BARRIER; return *atomic; } void (qb_atomic_int_set) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t newval) { *atomic = newval; QB_ATOMIC_MEMORY_BARRIER; } void * (qb_atomic_pointer_get) (volatile void *QB_GNUC_MAY_ALIAS * atomic) { QB_ATOMIC_MEMORY_BARRIER; return (void*)*atomic; } void (qb_atomic_pointer_set) (volatile void *QB_GNUC_MAY_ALIAS * atomic, void *newval) { *atomic = newval; QB_ATOMIC_MEMORY_BARRIER; } #endif /* QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED */ #endif /* HAVE_GCC_BUILTINS_FOR_SYNC_OPERATIONS */ #ifndef QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED int32_t (qb_atomic_int_get) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic) { return qb_atomic_int_get(atomic); } void (qb_atomic_int_set) (volatile int32_t QB_GNUC_MAY_ALIAS * atomic, int32_t newval) { qb_atomic_int_set(atomic, newval); } void * (qb_atomic_pointer_get) (volatile void *QB_GNUC_MAY_ALIAS * atomic) { return qb_atomic_pointer_get(atomic); } void (qb_atomic_pointer_set) (volatile void *QB_GNUC_MAY_ALIAS * atomic, void *newval) { qb_atomic_pointer_set(atomic, newval); } #endif /* !QB_ATOMIC_OP_MEMORY_BARRIER_NEEDED */ diff --git a/tests/check_ipc.c b/tests/check_ipc.c index aba1709..d2d59e6 100644 --- a/tests/check_ipc.c +++ b/tests/check_ipc.c @@ -1,2453 +1,2457 @@ /* * Copyright (c) 2010 Red Hat, Inc. * * All rights reserved. * * Author: Angus Salkeld * * This file is part of libqb. * * libqb is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 2.1 of the License, or * (at your option) any later version. * * libqb is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with libqb. If not, see . */ #include "os_base.h" #include #include #include #include #include #ifdef HAVE_GLIB #include #endif #include "check_common.h" #include #include #include #include #include #ifdef HAVE_FAILURE_INJECTION #include "_failure_injection.h" #endif #define NUM_STRESS_CONNECTIONS 5000 static char ipc_name[256]; #define DEFAULT_MAX_MSG_SIZE (8192*16) #ifndef __clang__ static int CALCULATED_DGRAM_MAX_MSG_SIZE = 0; #define DGRAM_MAX_MSG_SIZE \ (CALCULATED_DGRAM_MAX_MSG_SIZE == 0 ? \ CALCULATED_DGRAM_MAX_MSG_SIZE = qb_ipcc_verify_dgram_max_msg_size(DEFAULT_MAX_MSG_SIZE) : \ CALCULATED_DGRAM_MAX_MSG_SIZE) #define MAX_MSG_SIZE (ipc_type == QB_IPC_SOCKET ? DGRAM_MAX_MSG_SIZE : DEFAULT_MAX_MSG_SIZE) #else /* because of clang's 'variable length array in structure' extension will never be supported; assign default for SHM as we'll skip test that would use run-time established value (via qb_ipcc_verify_dgram_max_msg_size), anyway */ static const int MAX_MSG_SIZE = DEFAULT_MAX_MSG_SIZE; #endif /* The size the giant msg's data field needs to be to make * this the largests msg we can successfully send. */ #define GIANT_MSG_DATA_SIZE MAX_MSG_SIZE - sizeof(struct qb_ipc_response_header) - 8 static int enforce_server_buffer; static qb_ipcc_connection_t *conn; static enum qb_ipc_type ipc_type; static enum qb_loop_priority global_loop_prio = QB_LOOP_MED; static bool global_use_glib; static int global_pipefd[2]; enum my_msg_ids { IPC_MSG_REQ_TX_RX, IPC_MSG_RES_TX_RX, IPC_MSG_REQ_DISPATCH, IPC_MSG_RES_DISPATCH, IPC_MSG_REQ_BULK_EVENTS, IPC_MSG_RES_BULK_EVENTS, IPC_MSG_REQ_STRESS_EVENT, IPC_MSG_RES_STRESS_EVENT, IPC_MSG_REQ_SELF_FEED, IPC_MSG_RES_SELF_FEED, IPC_MSG_REQ_SERVER_FAIL, IPC_MSG_RES_SERVER_FAIL, IPC_MSG_REQ_SERVER_DISCONNECT, IPC_MSG_RES_SERVER_DISCONNECT, }; /* these 2 functions from pacemaker code */ static enum qb_ipcs_rate_limit conv_libqb_prio2ratelimit(enum qb_loop_priority prio) { /* this is an inversion of what libqb's qb_ipcs_request_rate_limit does */ enum qb_ipcs_rate_limit ret = QB_IPCS_RATE_NORMAL; switch (prio) { case QB_LOOP_LOW: ret = QB_IPCS_RATE_SLOW; break; case QB_LOOP_HIGH: ret = QB_IPCS_RATE_FAST; break; default: qb_log(LOG_DEBUG, "Invalid libqb's loop priority %d," " assuming QB_LOOP_MED", prio); /* fall-through */ case QB_LOOP_MED: break; } return ret; } #ifdef HAVE_GLIB static gint conv_prio_libqb2glib(enum qb_loop_priority prio) { gint ret = G_PRIORITY_DEFAULT; switch (prio) { case QB_LOOP_LOW: ret = G_PRIORITY_LOW; break; case QB_LOOP_HIGH: ret = G_PRIORITY_HIGH; break; default: qb_log(LOG_DEBUG, "Invalid libqb's loop priority %d," " assuming QB_LOOP_MED", prio); /* fall-through */ case QB_LOOP_MED: break; } return ret; } /* these 3 glue functions inspired from pacemaker, too */ static gboolean gio_source_prepare(GSource *source, gint *timeout) { qb_enter(); *timeout = 500; return FALSE; } static gboolean gio_source_check(GSource *source) { qb_enter(); return TRUE; } static gboolean gio_source_dispatch(GSource *source, GSourceFunc callback, gpointer user_data) { gboolean ret = G_SOURCE_CONTINUE; qb_enter(); if (callback) { ret = callback(user_data); } return ret; } static GSourceFuncs gio_source_funcs = { .prepare = gio_source_prepare, .check = gio_source_check, .dispatch = gio_source_dispatch, }; #endif /* Test Cases * * 1) basic send & recv different message sizes * * 2) send message to start dispatch (confirm receipt) * * 3) flow control * * 4) authentication * * 5) thread safety * * 6) cleanup * * 7) service availability * * 8) multiple services * * 9) setting perms on the sockets */ static qb_loop_t *my_loop; static qb_ipcs_service_t* s1; static int32_t turn_on_fc = QB_FALSE; static int32_t fc_enabled = 89; static int32_t send_event_on_created = QB_FALSE; static int32_t disconnect_after_created = QB_FALSE; static int32_t num_bulk_events = 10; static int32_t num_stress_events = 30000; static int32_t reference_count_test = QB_FALSE; static int32_t multiple_connections = QB_FALSE; static int32_t set_perms_on_socket = QB_FALSE; static int32_t exit_handler(int32_t rsignal, void *data) { qb_log(LOG_DEBUG, "caught signal %d", rsignal); qb_ipcs_destroy(s1); exit(0); } static void set_ipc_name(const char *prefix) { FILE *f; char process_name[256]; /* The process-unique part of the IPC name has already been decided * and stored in the file IPC_TEST_NAME_FILE */ f = fopen(IPC_TEST_NAME_FILE, "r"); if (f) { fgets(process_name, sizeof(process_name), f); + /* Remove any trailing LF that might be lurking */ + if (process_name[strlen(process_name)-1] == '\n') { + process_name[strlen(process_name)-1] = '\0'; + } fclose(f); snprintf(ipc_name, sizeof(ipc_name), "%.44s%s", prefix, process_name); } else { /* This is the old code, use only as a fallback */ static char t_sec[3] = ""; if (t_sec[0] == '\0') { const char *const found = strrchr(__TIME__, ':'); strncpy(t_sec, found ? found + 1 : "-", sizeof(t_sec) - 1); t_sec[sizeof(t_sec) - 1] = '\0'; } snprintf(ipc_name, sizeof(ipc_name), "%.44s%s%lX%.4x", prefix, t_sec, (unsigned long)getpid(), (unsigned) ((long) time(NULL) % (0x10000))); } } static int pipe_writer(int fd, int revents, void *data) { qb_enter(); static const char buf[8] = { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' }; ssize_t wbytes = 0, wbytes_sum = 0; //for (size_t i = 0; i < SIZE_MAX; i++) { for (size_t i = 0; i < 4096; i++) { wbytes_sum += wbytes; if ((wbytes = write(fd, buf, sizeof(buf))) == -1) { if (errno != EAGAIN) { perror("write"); exit(-1); } break; } } if (wbytes_sum > 0) { qb_log(LOG_DEBUG, "written %zd bytes", wbytes_sum); } qb_leave(); return 1; } static int pipe_reader(int fd, int revents, void *data) { qb_enter(); ssize_t rbytes, rbytes_sum = 0; size_t cnt = SIZE_MAX; char buf[4096] = { '\0' }; while ((rbytes = read(fd, buf, sizeof(buf))) > 0 && rbytes < cnt) { cnt -= rbytes; rbytes_sum += rbytes; } if (rbytes_sum > 0) { ck_assert(buf[0] != '\0'); /* avoid dead store elimination */ qb_log(LOG_DEBUG, "read %zd bytes", rbytes_sum); sleep(1); } qb_leave(); return 1; } #if HAVE_GLIB static gboolean gio_pipe_reader(void *data) { return (pipe_reader(*((int *) data), 0, NULL) > 0); } static gboolean gio_pipe_writer(void *data) { return (pipe_writer(*((int *) data), 0, NULL) > 0); } #endif static int32_t s1_msg_process_fn(qb_ipcs_connection_t *c, void *data, size_t size) { struct qb_ipc_request_header *req_pt = (struct qb_ipc_request_header *)data; struct qb_ipc_response_header response = { 0, }; ssize_t res; if (req_pt->id == IPC_MSG_REQ_TX_RX) { response.size = sizeof(struct qb_ipc_response_header); response.id = IPC_MSG_RES_TX_RX; response.error = 0; res = qb_ipcs_response_send(c, &response, response.size); if (res < 0) { qb_perror(LOG_INFO, "qb_ipcs_response_send"); } else if (res != response.size) { qb_log(LOG_DEBUG, "qb_ipcs_response_send %zd != %d", res, response.size); } if (turn_on_fc) { qb_ipcs_request_rate_limit(s1, QB_IPCS_RATE_OFF); } } else if (req_pt->id == IPC_MSG_REQ_DISPATCH) { response.size = sizeof(struct qb_ipc_response_header); response.id = IPC_MSG_RES_DISPATCH; response.error = 0; res = qb_ipcs_event_send(c, &response, sizeof(response)); if (res < 0) { qb_perror(LOG_INFO, "qb_ipcs_event_send"); } } else if (req_pt->id == IPC_MSG_REQ_BULK_EVENTS) { int32_t m; int32_t num; struct qb_ipcs_connection_stats_2 *stats; uint32_t max_size = MAX_MSG_SIZE; response.size = sizeof(struct qb_ipc_response_header); response.error = 0; stats = qb_ipcs_connection_stats_get_2(c, QB_FALSE); num = stats->event_q_length; free(stats); /* crazy large message */ res = qb_ipcs_event_send(c, &response, max_size*10); ck_assert_int_eq(res, -EMSGSIZE); /* send one event before responding */ res = qb_ipcs_event_send(c, &response, sizeof(response)); ck_assert_int_eq(res, sizeof(response)); response.id++; /* There should be one more item in the event queue now. */ stats = qb_ipcs_connection_stats_get_2(c, QB_FALSE); ck_assert_int_eq(stats->event_q_length - num, 1); free(stats); /* send response */ response.id = IPC_MSG_RES_BULK_EVENTS; res = qb_ipcs_response_send(c, &response, response.size); ck_assert_int_eq(res, sizeof(response)); /* send the rest of the events after the response */ for (m = 1; m < num_bulk_events; m++) { res = qb_ipcs_event_send(c, &response, sizeof(response)); if (res == -EAGAIN || res == -ENOBUFS) { /* retry */ usleep(1000); m--; continue; } ck_assert_int_eq(res, sizeof(response)); response.id++; } } else if (req_pt->id == IPC_MSG_REQ_STRESS_EVENT) { struct { struct qb_ipc_response_header hdr __attribute__ ((aligned(8))); char data[GIANT_MSG_DATA_SIZE] __attribute__ ((aligned(8))); uint32_t sent_msgs __attribute__ ((aligned(8))); } __attribute__ ((aligned(8))) giant_event_send; int32_t m; response.size = sizeof(struct qb_ipc_response_header); response.error = 0; response.id = IPC_MSG_RES_STRESS_EVENT; res = qb_ipcs_response_send(c, &response, response.size); ck_assert_int_eq(res, sizeof(response)); giant_event_send.hdr.error = 0; giant_event_send.hdr.id = IPC_MSG_RES_STRESS_EVENT; for (m = 0; m < num_stress_events; m++) { size_t sent_len = sizeof(struct qb_ipc_response_header); if (((m+1) % 1000) == 0) { sent_len = sizeof(giant_event_send); giant_event_send.sent_msgs = m + 1; } giant_event_send.hdr.size = sent_len; res = qb_ipcs_event_send(c, &giant_event_send, sent_len); if (res < 0) { if (res == -EAGAIN || res == -ENOBUFS) { /* yield to the receive process */ usleep(1000); m--; continue; } else { qb_perror(LOG_DEBUG, "sending stress events"); ck_assert_int_eq(res, sent_len); } } else if (((m+1) % 1000) == 0) { qb_log(LOG_DEBUG, "SENT: %d stress events sent", m+1); } giant_event_send.hdr.id++; } } else if (req_pt->id == IPC_MSG_REQ_SELF_FEED) { if (pipe(global_pipefd) != 0) { perror("pipefd"); ck_assert(0); } fcntl(global_pipefd[0], F_SETFL, O_NONBLOCK); fcntl(global_pipefd[1], F_SETFL, O_NONBLOCK); if (global_use_glib) { #ifdef HAVE_GLIB GSource *source_r, *source_w; source_r = g_source_new(&gio_source_funcs, sizeof(GSource)); source_w = g_source_new(&gio_source_funcs, sizeof(GSource)); ck_assert(source_r != NULL && source_w != NULL); g_source_set_priority(source_r, conv_prio_libqb2glib(QB_LOOP_HIGH)); g_source_set_priority(source_w, conv_prio_libqb2glib(QB_LOOP_HIGH)); g_source_set_can_recurse(source_r, FALSE); g_source_set_can_recurse(source_w, FALSE); g_source_set_callback(source_r, gio_pipe_reader, &global_pipefd[0], NULL); g_source_set_callback(source_w, gio_pipe_writer, &global_pipefd[1], NULL); g_source_add_unix_fd(source_r, global_pipefd[0], G_IO_IN); g_source_add_unix_fd(source_w, global_pipefd[1], G_IO_OUT); g_source_attach(source_r, NULL); g_source_attach(source_w, NULL); #else ck_assert(0); #endif } else { qb_loop_poll_add(my_loop, QB_LOOP_HIGH, global_pipefd[1], POLLOUT|POLLERR, NULL, pipe_writer); qb_loop_poll_add(my_loop, QB_LOOP_HIGH, global_pipefd[0], POLLIN|POLLERR, NULL, pipe_reader); } } else if (req_pt->id == IPC_MSG_REQ_SERVER_FAIL) { exit(0); } else if (req_pt->id == IPC_MSG_REQ_SERVER_DISCONNECT) { multiple_connections = QB_FALSE; qb_ipcs_disconnect(c); } return 0; } static int32_t my_job_add(enum qb_loop_priority p, void *data, qb_loop_job_dispatch_fn fn) { return qb_loop_job_add(my_loop, p, data, fn); } static int32_t my_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t events, void *data, qb_ipcs_dispatch_fn_t fn) { return qb_loop_poll_add(my_loop, p, fd, events, data, fn); } static int32_t my_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t events, void *data, qb_ipcs_dispatch_fn_t fn) { return qb_loop_poll_mod(my_loop, p, fd, events, data, fn); } static int32_t my_dispatch_del(int32_t fd) { return qb_loop_poll_del(my_loop, fd); } /* taken from examples/ipcserver.c, with s/my_g/gio/ */ #ifdef HAVE_GLIB #include static qb_array_t *gio_map; static GMainLoop *glib_loop; struct gio_to_qb_poll { int32_t is_used; int32_t events; int32_t source; int32_t fd; void *data; qb_ipcs_dispatch_fn_t fn; enum qb_loop_priority p; }; static gboolean gio_read_socket(GIOChannel * gio, GIOCondition condition, gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; gint fd = g_io_channel_unix_get_fd(gio); qb_enter(); return (adaptor->fn(fd, condition, adaptor->data) == 0); } static void gio_poll_destroy(gpointer data) { struct gio_to_qb_poll *adaptor = (struct gio_to_qb_poll *)data; adaptor->is_used--; if (adaptor->is_used == 0) { qb_log(LOG_DEBUG, "fd %d adaptor destroyed\n", adaptor->fd); adaptor->fd = 0; adaptor->source = 0; } } static int32_t gio_dispatch_update(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn, gboolean is_new) { struct gio_to_qb_poll *adaptor; GIOChannel *channel; int32_t res = 0; qb_enter(); res = qb_array_index(gio_map, fd, (void **)&adaptor); if (res < 0) { return res; } if (adaptor->is_used && adaptor->source) { if (is_new) { return -EEXIST; } g_source_remove(adaptor->source); adaptor->source = 0; } channel = g_io_channel_unix_new(fd); if (!channel) { return -ENOMEM; } adaptor->fn = fn; adaptor->events = evts; adaptor->data = data; adaptor->p = p; adaptor->is_used++; adaptor->fd = fd; adaptor->source = g_io_add_watch_full(channel, conv_prio_libqb2glib(p), evts, gio_read_socket, adaptor, gio_poll_destroy); /* we are handing the channel off to be managed by mainloop now. * remove our reference. */ g_io_channel_unref(channel); return 0; } static int32_t gio_dispatch_add(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return gio_dispatch_update(p, fd, evts, data, fn, TRUE); } static int32_t gio_dispatch_mod(enum qb_loop_priority p, int32_t fd, int32_t evts, void *data, qb_ipcs_dispatch_fn_t fn) { return gio_dispatch_update(p, fd, evts, data, fn, FALSE); } static int32_t gio_dispatch_del(int32_t fd) { struct gio_to_qb_poll *adaptor; if (qb_array_index(gio_map, fd, (void **)&adaptor) == 0) { g_source_remove(adaptor->source); adaptor->source = 0; } return 0; } #endif /* HAVE_GLIB */ static int32_t s1_connection_closed(qb_ipcs_connection_t *c) { if (multiple_connections) { return 0; } /* Stop the connection being freed when we call qb_ipcs_disconnect() in the callback */ if (disconnect_after_created == QB_TRUE) { disconnect_after_created = QB_FALSE; return 1; } qb_enter(); qb_leave(); return 0; } static void outq_flush (void *data) { static int i = 0; struct cs_ipcs_conn_context *cnx; cnx = qb_ipcs_context_get(data); qb_log(LOG_DEBUG,"iter %u\n", i); i++; if (i == 2) { qb_ipcs_destroy(s1); s1 = NULL; } /* if the reference counting is not working, this should fail * for i > 1. */ qb_ipcs_event_send(data, "test", 4); assert(memcmp(cnx, "test", 4) == 0); if (i < 5) { qb_loop_job_add(my_loop, QB_LOOP_HIGH, data, outq_flush); } else { /* this single unref should clean everything up. */ qb_ipcs_connection_unref(data); qb_log(LOG_INFO, "end of test, stopping loop"); qb_loop_stop(my_loop); } } static void s1_connection_destroyed(qb_ipcs_connection_t *c) { if (multiple_connections) { return; } qb_enter(); if (reference_count_test) { struct cs_ipcs_conn_context *cnx; cnx = qb_ipcs_context_get(c); free(cnx); } else { qb_loop_stop(my_loop); } qb_leave(); } static int32_t s1_connection_accept(qb_ipcs_connection_t *c, uid_t uid, gid_t gid) { if (set_perms_on_socket) { qb_ipcs_connection_auth_set(c, 555, 741, S_IRWXU|S_IRWXG|S_IROTH|S_IWOTH); } return 0; } static void s1_connection_created(qb_ipcs_connection_t *c) { uint32_t max = MAX_MSG_SIZE; if (multiple_connections) { return; } if (send_event_on_created) { struct qb_ipc_response_header response; int32_t res; response.size = sizeof(struct qb_ipc_response_header); response.id = IPC_MSG_RES_DISPATCH; response.error = 0; res = qb_ipcs_event_send(c, &response, sizeof(response)); ck_assert_int_eq(res, response.size); } if (reference_count_test) { struct cs_ipcs_conn_context *context; qb_ipcs_connection_ref(c); qb_loop_job_add(my_loop, QB_LOOP_HIGH, c, outq_flush); context = calloc(1, 20); memcpy(context, "test", 4); qb_ipcs_context_set(c, context); } ck_assert_int_le(max, qb_ipcs_connection_get_buffer_size(c)); } static volatile sig_atomic_t usr1_bit; static void usr1_bit_setter(int signal) { if (signal == SIGUSR1) { usr1_bit = 1; } } #define READY_SIGNALLER(name, data_arg) void (name)(void *data_arg) typedef READY_SIGNALLER(ready_signaller_fn, ); static READY_SIGNALLER(usr1_signaller, parent_target) { kill(*((pid_t *) parent_target), SIGUSR1); } #define NEW_PROCESS_RUNNER(name, ready_signaller_arg, signaller_data_arg, data_arg) \ void (name)(ready_signaller_fn ready_signaller_arg, \ void *signaller_data_arg, void *data_arg) typedef NEW_PROCESS_RUNNER(new_process_runner_fn, , , ); static NEW_PROCESS_RUNNER(run_ipc_server, ready_signaller, signaller_data, data) { int32_t res; qb_loop_signal_handle handle; struct qb_ipcs_service_handlers sh = { .connection_accept = s1_connection_accept, .connection_created = s1_connection_created, .msg_process = s1_msg_process_fn, .connection_destroyed = s1_connection_destroyed, .connection_closed = s1_connection_closed, }; struct qb_ipcs_poll_handlers ph; uint32_t max_size = MAX_MSG_SIZE; my_loop = qb_loop_create(); qb_loop_signal_add(my_loop, QB_LOOP_HIGH, SIGTERM, NULL, exit_handler, &handle); s1 = qb_ipcs_create(ipc_name, 4, ipc_type, &sh); ck_assert(s1 != 0); if (global_loop_prio != QB_LOOP_MED) { qb_ipcs_request_rate_limit(s1, conv_libqb_prio2ratelimit(global_loop_prio)); } if (global_use_glib) { #ifdef HAVE_GLIB ph = (struct qb_ipcs_poll_handlers) { .job_add = NULL, .dispatch_add = gio_dispatch_add, .dispatch_mod = gio_dispatch_mod, .dispatch_del = gio_dispatch_del, }; glib_loop = g_main_loop_new(NULL, FALSE); gio_map = qb_array_create_2(16, sizeof(struct gio_to_qb_poll), 1); ck_assert(gio_map != NULL); #else ck_assert(0); #endif } else { ph = (struct qb_ipcs_poll_handlers) { .job_add = my_job_add, .dispatch_add = my_dispatch_add, .dispatch_mod = my_dispatch_mod, .dispatch_del = my_dispatch_del, }; } if (enforce_server_buffer) { qb_ipcs_enforce_buffer_size(s1, max_size); } qb_ipcs_poll_handlers_set(s1, &ph); res = qb_ipcs_run(s1); ck_assert_int_eq(res, 0); if (ready_signaller != NULL) { ready_signaller(signaller_data); } if (global_use_glib) { #ifdef HAVE_GLIB g_main_loop_run(glib_loop); #endif } else { qb_loop_run(my_loop); } qb_log(LOG_DEBUG, "loop finished - done ..."); } static pid_t run_function_in_new_process(const char *role, new_process_runner_fn new_process_runner, void *data) { char formatbuf[1024]; pid_t parent_target, pid1, pid2; struct sigaction orig_sa, purpose_sa; sigset_t orig_mask, purpose_mask, purpose_clear_mask; sigemptyset(&purpose_mask); sigaddset(&purpose_mask, SIGUSR1); sigprocmask(SIG_BLOCK, &purpose_mask, &orig_mask); purpose_clear_mask = orig_mask; sigdelset(&purpose_clear_mask, SIGUSR1); purpose_sa.sa_handler = usr1_bit_setter; purpose_sa.sa_mask = purpose_mask; purpose_sa.sa_flags = SA_RESTART; /* Double-fork so the servers can be reaped in a timely manner */ parent_target = getpid(); pid1 = fork(); if (pid1 == 0) { pid2 = fork(); if (pid2 == -1) { fprintf (stderr, "Can't fork twice\n"); exit(0); } if (pid2 == 0) { sigprocmask(SIG_SETMASK, &orig_mask, NULL); if (role == NULL) { qb_log_format_set(QB_LOG_STDERR, "lib/%f|%l[%P] %b"); } else { snprintf(formatbuf, sizeof(formatbuf), "lib/%%f|%%l|%s[%%P] %%b", role); qb_log_format_set(QB_LOG_STDERR, formatbuf); } new_process_runner(usr1_signaller, &parent_target, data); exit(0); } else { waitpid(pid2, NULL, 0); exit(0); } } usr1_bit = 0; /* XXX assume never fails */ sigaction(SIGUSR1, &purpose_sa, &orig_sa); do { /* XXX assume never fails with EFAULT */ sigsuspend(&purpose_clear_mask); } while (usr1_bit != 1); usr1_bit = 0; sigprocmask(SIG_SETMASK, &orig_mask, NULL); /* give children a slight/non-strict scheduling advantage */ sched_yield(); return pid1; } static void request_server_exit(void) { struct qb_ipc_request_header req_header; struct qb_ipc_response_header res_header; struct iovec iov[1]; int32_t res; /* * tell the server to exit */ req_header.id = IPC_MSG_REQ_SERVER_FAIL; req_header.size = sizeof(struct qb_ipc_request_header); iov[0].iov_len = req_header.size; - iov[0].iov_base = &req_header; + iov[0].iov_base = (void*)&req_header; ck_assert_int_eq(QB_TRUE, qb_ipcc_is_connected(conn)); res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), -1); /* * confirm we get -ENOTCONN or ECONNRESET */ if (res != -ECONNRESET && res != -ENOTCONN) { qb_log(LOG_ERR, "id:%d size:%d", res_header.id, res_header.size); ck_assert_int_eq(res, -ENOTCONN); } } static void kill_server(pid_t pid) { kill(pid, SIGTERM); waitpid(pid, NULL, 0); } static int32_t verify_graceful_stop(pid_t pid) { int wait_rc = 0; int status = 0; int rc = 0; int tries; /* We need the server to be able to exit by itself */ for (tries = 10; tries >= 0; tries--) { sleep(1); wait_rc = waitpid(pid, &status, WNOHANG); if (wait_rc > 0) { break; } } ck_assert_int_eq(wait_rc, pid); rc = WIFEXITED(status); if (rc) { rc = WEXITSTATUS(status); ck_assert_int_eq(rc, 0); } else { ck_assert(rc != 0); } return 0; } struct my_req { struct qb_ipc_request_header hdr; char message[1024 * 1024]; }; static struct my_req request; static int32_t send_and_check(int32_t req_id, uint32_t size, int32_t ms_timeout, int32_t expect_perfection) { struct qb_ipc_response_header res_header; int32_t res; int32_t try_times = 0; uint32_t max_size = MAX_MSG_SIZE; request.hdr.id = req_id; request.hdr.size = sizeof(struct qb_ipc_request_header) + size; /* check that we can't send a message that is too big * and we get the right return code. */ res = qb_ipcc_send(conn, &request, max_size*2); ck_assert_int_eq(res, -EMSGSIZE); repeat_send: res = qb_ipcc_send(conn, &request, request.hdr.size); try_times++; if (res < 0) { if (res == -EAGAIN && try_times < 10) { goto repeat_send; } else { if (res == -EAGAIN && try_times >= 10) { fc_enabled = QB_TRUE; } errno = -res; qb_perror(LOG_INFO, "qb_ipcc_send"); return res; } } if (req_id == IPC_MSG_REQ_DISPATCH) { res = qb_ipcc_event_recv(conn, &res_header, sizeof(struct qb_ipc_response_header), ms_timeout); } else { res = qb_ipcc_recv(conn, &res_header, sizeof(struct qb_ipc_response_header), ms_timeout); } if (res == -EINTR) { return -1; } if (res == -EAGAIN || res == -ETIMEDOUT) { fc_enabled = QB_TRUE; qb_perror(LOG_DEBUG, "qb_ipcc_recv"); return res; } if (expect_perfection) { ck_assert_int_eq(res, sizeof(struct qb_ipc_response_header)); ck_assert_int_eq(res_header.id, req_id + 1); ck_assert_int_eq(res_header.size, sizeof(struct qb_ipc_response_header)); } return res; } static int32_t process_async_connect(int32_t fd, int32_t revents, void *data) { qb_loop_t *cl = (qb_loop_t *)data; int res; res = qb_ipcc_connect_continue(conn); ck_assert_int_eq(res, 0); qb_loop_stop(cl); return 0; } static void test_ipc_connect_async(void) { struct qb_ipc_request_header req_header; struct qb_ipc_response_header res_header; int32_t res; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; int connect_fd; struct iovec iov[1]; static qb_loop_t *cl; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); conn = qb_ipcc_connect_async(ipc_name, max_size, &connect_fd); ck_assert(conn != NULL); cl = qb_loop_create(); res = qb_loop_poll_add(cl, QB_LOOP_MED, connect_fd, POLLIN, cl, process_async_connect); ck_assert_int_eq(res, 0); qb_loop_run(cl); /* Send some data */ req_header.id = IPC_MSG_REQ_TX_RX; req_header.size = sizeof(struct qb_ipc_request_header); iov[0].iov_len = req_header.size; - iov[0].iov_base = &req_header; + iov[0].iov_base = (void*)&req_header; res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), 5000); ck_assert_int_ge(res, 0); request_server_exit(); verify_graceful_stop(pid); qb_ipcc_disconnect(conn); } static void test_ipc_txrx_timeout(void) { struct qb_ipc_request_header req_header; struct qb_ipc_response_header res_header; struct iovec iov[1]; int32_t res; int32_t c = 0; int32_t j = 0; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); /* The dispatch response will only come over * the event channel, we want to verify the receive times * out when an event is returned with no response */ req_header.id = IPC_MSG_REQ_DISPATCH; req_header.size = sizeof(struct qb_ipc_request_header); iov[0].iov_len = req_header.size; - iov[0].iov_base = &req_header; + iov[0].iov_base = (void*)&req_header; res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), 5000); ck_assert_int_eq(res, -ETIMEDOUT); request_server_exit(); verify_graceful_stop(pid); /* * this needs to free up the shared mem */ qb_ipcc_disconnect(conn); } static int32_t recv_timeout = -1; static void test_ipc_txrx(void) { int32_t j; int32_t c = 0; size_t size; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); size = QB_MIN(sizeof(struct qb_ipc_request_header), 64); for (j = 1; j < 19; j++) { size *= 2; if (size >= max_size) break; if (send_and_check(IPC_MSG_REQ_TX_RX, size, recv_timeout, QB_TRUE) < 0) { break; } } if (turn_on_fc) { /* can't signal server to shutdown if flow control is on */ ck_assert_int_eq(fc_enabled, QB_TRUE); qb_ipcc_disconnect(conn); /* TODO - figure out why this sleep is necessary */ sleep(1); kill_server(pid); } else { request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } } static void test_ipc_getauth(void) { int32_t j; int32_t c = 0; pid_t pid; pid_t spid; uid_t suid; gid_t sgid; int res; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); res = qb_ipcc_auth_get(NULL, NULL, NULL, NULL); ck_assert(res == -EINVAL); res = qb_ipcc_auth_get(conn, &spid, &suid, &sgid); ck_assert(res == 0); #ifndef HAVE_GETPEEREID /* GETPEEREID doesn't return a PID */ ck_assert(spid != 0); #endif ck_assert(suid == getuid()); ck_assert(sgid == getgid()); request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } static void test_ipc_exit(void) { struct qb_ipc_request_header req_header; struct qb_ipc_response_header res_header; struct iovec iov[1]; int32_t res; int32_t c = 0; int32_t j = 0; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); req_header.id = IPC_MSG_REQ_TX_RX; req_header.size = sizeof(struct qb_ipc_request_header); iov[0].iov_len = req_header.size; - iov[0].iov_base = &req_header; + iov[0].iov_base = (void*)&req_header; res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), -1); ck_assert_int_eq(res, sizeof(struct qb_ipc_response_header)); request_server_exit(); verify_graceful_stop(pid); /* * this needs to free up the shared mem */ qb_ipcc_disconnect(conn); } START_TEST(test_ipc_exit_us) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); recv_timeout = 5000; test_ipc_exit(); qb_leave(); } END_TEST START_TEST(test_ipc_exit_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); recv_timeout = 1000; test_ipc_exit(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_shm_timeout) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_txrx_timeout(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_us_timeout) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_txrx_timeout(); qb_leave(); } END_TEST START_TEST(test_ipc_shm_connect_async) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_connect_async(); qb_leave(); } END_TEST START_TEST(test_ipc_us_connect_async) { qb_enter(); - ipc_type = QB_IPC_SHM; + ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_connect_async(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_shm_getauth) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_getauth(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_us_getauth) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_getauth(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_shm_tmo) { qb_enter(); turn_on_fc = QB_FALSE; ipc_type = QB_IPC_SHM; set_ipc_name(__func__); recv_timeout = 1000; test_ipc_txrx(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_shm_block) { qb_enter(); turn_on_fc = QB_FALSE; ipc_type = QB_IPC_SHM; set_ipc_name(__func__); recv_timeout = -1; test_ipc_txrx(); qb_leave(); } END_TEST START_TEST(test_ipc_fc_shm) { qb_enter(); turn_on_fc = QB_TRUE; ipc_type = QB_IPC_SHM; recv_timeout = 500; set_ipc_name(__func__); test_ipc_txrx(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_us_block) { qb_enter(); turn_on_fc = QB_FALSE; ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); recv_timeout = -1; test_ipc_txrx(); qb_leave(); } END_TEST START_TEST(test_ipc_txrx_us_tmo) { qb_enter(); turn_on_fc = QB_FALSE; ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); recv_timeout = 1000; test_ipc_txrx(); qb_leave(); } END_TEST START_TEST(test_ipc_fc_us) { qb_enter(); turn_on_fc = QB_TRUE; ipc_type = QB_IPC_SOCKET; recv_timeout = 500; set_ipc_name(__func__); test_ipc_txrx(); qb_leave(); } END_TEST struct my_res { struct qb_ipc_response_header hdr; char message[1024 * 1024]; }; struct dispatch_data { pid_t server_pid; enum my_msg_ids msg_type; uint32_t repetitions; }; static inline NEW_PROCESS_RUNNER(client_dispatch, ready_signaller, signaller_data, data) { uint32_t max_size = MAX_MSG_SIZE; int32_t size; int32_t c = 0; int32_t j; pid_t server_pid = ((struct dispatch_data *) data)->server_pid; enum my_msg_ids msg_type = ((struct dispatch_data *) data)->msg_type; do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(server_pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); if (ready_signaller != NULL) { ready_signaller(signaller_data); } size = QB_MIN(sizeof(struct qb_ipc_request_header), 64); for (uint32_t r = ((struct dispatch_data *) data)->repetitions; r > 0; r--) { for (j = 1; j < 19; j++) { size *= 2; if (size >= max_size) break; if (send_and_check(msg_type, size, recv_timeout, QB_TRUE) < 0) { break; } } } } static void test_ipc_dispatch(void) { pid_t pid; struct dispatch_data data; pid = run_function_in_new_process(NULL, run_ipc_server, NULL); ck_assert(pid != -1); data = (struct dispatch_data){.server_pid = pid, .msg_type = IPC_MSG_REQ_DISPATCH, .repetitions = 1}; client_dispatch(NULL, NULL, (void *) &data); request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } START_TEST(test_ipc_dispatch_us) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_dispatch(); qb_leave(); } END_TEST static int32_t events_received; static int32_t count_stress_events(int32_t fd, int32_t revents, void *data) { struct { struct qb_ipc_response_header hdr __attribute__ ((aligned(8))); char data[GIANT_MSG_DATA_SIZE] __attribute__ ((aligned(8))); uint32_t sent_msgs __attribute__ ((aligned(8))); } __attribute__ ((aligned(8))) giant_event_recv; qb_loop_t *cl = (qb_loop_t*)data; int32_t res; res = qb_ipcc_event_recv(conn, &giant_event_recv, sizeof(giant_event_recv), -1); if (res > 0) { events_received++; if ((events_received % 1000) == 0) { qb_log(LOG_DEBUG, "RECV: %d stress events processed", events_received); if (res != sizeof(giant_event_recv)) { qb_log(LOG_DEBUG, "Unexpected recv size, expected %d got %d", sizeof(giant_event_recv), res); ck_assert_int_eq(res, sizeof(giant_event_recv)); } else if (giant_event_recv.sent_msgs != events_received) { qb_log(LOG_DEBUG, "Server event mismatch. Server thinks we got %d msgs, but we only received %d", giant_event_recv.sent_msgs, events_received); /* This indicates that data corruption is occurring. Since the events * received is placed at the end of the giant msg, it is possible * that buffers were not allocated correctly resulting in us * reading/writing to uninitialized memeory at some point. */ ck_assert_int_eq(giant_event_recv.sent_msgs, events_received); } } } else if (res != -EAGAIN) { qb_perror(LOG_DEBUG, "count_stress_events"); qb_loop_stop(cl); return -1; } if (events_received >= num_stress_events) { qb_loop_stop(cl); return -1; } return 0; } static int32_t count_bulk_events(int32_t fd, int32_t revents, void *data) { qb_loop_t *cl = (qb_loop_t*)data; struct qb_ipc_response_header res_header; int32_t res; res = qb_ipcc_event_recv(conn, &res_header, sizeof(struct qb_ipc_response_header), -1); if (res > 0) { events_received++; } if (events_received >= num_bulk_events) { qb_loop_stop(cl); return -1; } return 0; } static void test_ipc_stress_connections(void) { int32_t c = 0; int32_t j = 0; uint32_t max_size = MAX_MSG_SIZE; int32_t connections = 0; pid_t pid; multiple_connections = QB_TRUE; qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_CLEAR_ALL, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_INFO); qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); for (connections = 1; connections < NUM_STRESS_CONNECTIONS; connections++) { if (conn) { qb_ipcc_disconnect(conn); conn = NULL; } do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); sleep(1); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); if (((connections+1) % 1000) == 0) { qb_log(LOG_INFO, "%d ipc connections made", connections+1); } } multiple_connections = QB_FALSE; /* Re-enable logging here so we get the "Free'ing" message which allows for resources.test to clear up after us if needed */ qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_CLEAR_ALL, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } static void test_ipc_bulk_events(void) { int32_t c = 0; int32_t j = 0; pid_t pid; int32_t res; qb_loop_t *cl; int32_t fd; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); events_received = 0; cl = qb_loop_create(); res = qb_ipcc_fd_get(conn, &fd); ck_assert_int_eq(res, 0); res = qb_loop_poll_add(cl, QB_LOOP_MED, fd, POLLIN, cl, count_bulk_events); ck_assert_int_eq(res, 0); res = send_and_check(IPC_MSG_REQ_BULK_EVENTS, 0, recv_timeout, QB_TRUE); ck_assert_int_eq(res, sizeof(struct qb_ipc_response_header)); qb_loop_run(cl); ck_assert_int_eq(events_received, num_bulk_events); request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } static void test_ipc_stress_test(void) { struct { struct qb_ipc_request_header hdr __attribute__ ((aligned(8))); char data[GIANT_MSG_DATA_SIZE] __attribute__ ((aligned(8))); uint32_t sent_msgs __attribute__ ((aligned(8))); } __attribute__ ((aligned(8))) giant_req; struct qb_ipc_response_header res_header; struct iovec iov[1]; int32_t c = 0; int32_t j = 0; pid_t pid; int32_t res; qb_loop_t *cl; int32_t fd; uint32_t max_size = MAX_MSG_SIZE; /* This looks strange, but it serves an important purpose. * This test forces the server to enforce the MAX_MSG_SIZE * limit from the server side, which overrides the client's * buffer limit. To verify this functionality is working * we set the client limit lower than what the server * is enforcing. */ int32_t client_buf_size = max_size - 1024; int32_t real_buf_size; enforce_server_buffer = 1; pid = run_function_in_new_process("server", run_ipc_server, NULL); enforce_server_buffer = 0; ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, client_buf_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); real_buf_size = qb_ipcc_get_buffer_size(conn); ck_assert_int_ge(real_buf_size, max_size); qb_log(LOG_DEBUG, "Testing %d iterations of EVENT msg passing.", num_stress_events); events_received = 0; cl = qb_loop_create(); res = qb_ipcc_fd_get(conn, &fd); ck_assert_int_eq(res, 0); res = qb_loop_poll_add(cl, QB_LOOP_MED, fd, POLLIN, cl, count_stress_events); ck_assert_int_eq(res, 0); res = send_and_check(IPC_MSG_REQ_STRESS_EVENT, 0, recv_timeout, QB_TRUE); qb_loop_run(cl); ck_assert_int_eq(events_received, num_stress_events); giant_req.hdr.id = IPC_MSG_REQ_SERVER_FAIL; giant_req.hdr.size = sizeof(giant_req); if (giant_req.hdr.size <= client_buf_size) { ck_assert_int_eq(1, 0); } iov[0].iov_len = giant_req.hdr.size; - iov[0].iov_base = &giant_req; + iov[0].iov_base = (void*)&giant_req; res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), -1); if (res != -ECONNRESET && res != -ENOTCONN) { qb_log(LOG_ERR, "id:%d size:%d", res_header.id, res_header.size); ck_assert_int_eq(res, -ENOTCONN); } qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } #ifndef __clang__ /* see variable length array in structure' at the top */ START_TEST(test_ipc_stress_test_us) { qb_enter(); send_event_on_created = QB_FALSE; ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_stress_test(); qb_leave(); } END_TEST #endif START_TEST(test_ipc_stress_connections_us) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_stress_connections(); qb_leave(); } END_TEST START_TEST(test_ipc_bulk_events_us) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_bulk_events(); qb_leave(); } END_TEST static READY_SIGNALLER(connected_signaller, _) { request_server_exit(); } START_TEST(test_ipc_us_native_prio_dlock) { pid_t server_pid, alphaclient_pid; struct dispatch_data data; qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); /* this is to demonstrate that native event loop can deal even with "extreme" priority disproportions */ global_loop_prio = QB_LOOP_LOW; multiple_connections = QB_TRUE; recv_timeout = -1; server_pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(server_pid != -1); data = (struct dispatch_data){.server_pid = server_pid, .msg_type = IPC_MSG_REQ_SELF_FEED, .repetitions = 1}; alphaclient_pid = run_function_in_new_process("alphaclient", client_dispatch, (void *) &data); ck_assert(alphaclient_pid != -1); //sleep(1); sched_yield(); data.repetitions = 0; client_dispatch(connected_signaller, NULL, (void *) &data); verify_graceful_stop(server_pid); multiple_connections = QB_FALSE; qb_leave(); } END_TEST #if HAVE_GLIB START_TEST(test_ipc_us_glib_prio_dlock) { pid_t server_pid, alphaclient_pid; struct dispatch_data data; qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); global_use_glib = QB_TRUE; /* this is to make the test pass at all, since GLib is strict on priorities -- QB_LOOP_MED or lower would fail for sure */ global_loop_prio = QB_LOOP_HIGH; multiple_connections = QB_TRUE; recv_timeout = -1; server_pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(server_pid != -1); data = (struct dispatch_data){.server_pid = server_pid, .msg_type = IPC_MSG_REQ_SELF_FEED, .repetitions = 1}; alphaclient_pid = run_function_in_new_process("alphaclient", client_dispatch, (void *) &data); ck_assert(alphaclient_pid != -1); //sleep(1); sched_yield(); data.repetitions = 0; client_dispatch(connected_signaller, NULL, (void *) &data); verify_graceful_stop(server_pid); multiple_connections = QB_FALSE; global_loop_prio = QB_LOOP_MED; global_use_glib = QB_FALSE; qb_leave(); } END_TEST #endif static void test_ipc_event_on_created(void) { int32_t c = 0; int32_t j = 0; pid_t pid; int32_t res; qb_loop_t *cl; int32_t fd; uint32_t max_size = MAX_MSG_SIZE; num_bulk_events = 1; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); events_received = 0; cl = qb_loop_create(); res = qb_ipcc_fd_get(conn, &fd); ck_assert_int_eq(res, 0); res = qb_loop_poll_add(cl, QB_LOOP_MED, fd, POLLIN, cl, count_bulk_events); ck_assert_int_eq(res, 0); qb_loop_run(cl); ck_assert_int_eq(events_received, num_bulk_events); request_server_exit(); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } START_TEST(test_ipc_event_on_created_us) { qb_enter(); send_event_on_created = QB_TRUE; ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_event_on_created(); qb_leave(); } END_TEST static void test_ipc_disconnect_after_created(void) { struct qb_ipc_request_header req_header; struct qb_ipc_response_header res_header; struct iovec iov[1]; int32_t c = 0; int32_t j = 0; pid_t pid; int32_t res; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); ck_assert_int_eq(QB_TRUE, qb_ipcc_is_connected(conn)); req_header.id = IPC_MSG_REQ_SERVER_DISCONNECT; req_header.size = sizeof(struct qb_ipc_request_header); iov[0].iov_len = req_header.size; - iov[0].iov_base = &req_header; + iov[0].iov_base = (void*)&req_header; res = qb_ipcc_sendv_recv(conn, iov, 1, &res_header, sizeof(struct qb_ipc_response_header), -1); /* * confirm we get -ENOTCONN or -ECONNRESET */ if (res != -ECONNRESET && res != -ENOTCONN) { qb_log(LOG_ERR, "id:%d size:%d", res_header.id, res_header.size); ck_assert_int_eq(res, -ENOTCONN); } ck_assert_int_eq(QB_FALSE, qb_ipcc_is_connected(conn)); qb_ipcc_disconnect(conn); sleep(1); /* Give it time to stop */ kill_server(pid); } START_TEST(test_ipc_disconnect_after_created_us) { qb_enter(); disconnect_after_created = QB_TRUE; ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_disconnect_after_created(); qb_leave(); } END_TEST static void test_ipc_server_fail(void) { int32_t j; int32_t c = 0; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); request_server_exit(); if (_fi_unlink_inject_failure == QB_TRUE) { _fi_truncate_called = _fi_openat_called = 0; } ck_assert_int_eq(QB_FALSE, qb_ipcc_is_connected(conn)); qb_ipcc_disconnect(conn); if (_fi_unlink_inject_failure == QB_TRUE) { ck_assert_int_ne(_fi_truncate_called + _fi_openat_called, 0); } verify_graceful_stop(pid); } START_TEST(test_ipc_server_fail_soc) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_server_fail(); qb_leave(); } END_TEST START_TEST(test_ipc_dispatch_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_dispatch(); qb_leave(); } END_TEST START_TEST(test_ipc_stress_test_shm) { qb_enter(); send_event_on_created = QB_FALSE; ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_stress_test(); qb_leave(); } END_TEST START_TEST(test_ipc_stress_connections_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_stress_connections(); qb_leave(); } END_TEST // Check perms uses illegal access to libqb internals // DO NOT try this at home. #include "../lib/ipc_int.h" #include "../lib/ringbuffer_int.h" START_TEST(test_ipc_server_perms) { pid_t pid; struct stat st; int j; uint32_t max_size; int res; int c = 0; // Can only test this if we are root if (getuid() != 0) { return; } ipc_type = QB_IPC_SHM; set_perms_on_socket = QB_TRUE; max_size = MAX_MSG_SIZE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); /* Check perms - uses illegal access to libqb internals */ /* BSD uses /var/run for sockets so we can't alter the perms on the directory */ #ifdef __linux__ char sockdir[PATH_MAX]; strcpy(sockdir, conn->request.u.shm.rb->shared_hdr->hdr_path); *strrchr(sockdir, '/') = 0; res = stat(sockdir, &st); ck_assert_int_eq(res, 0); ck_assert(st.st_mode & S_IRWXG); ck_assert_int_eq(st.st_uid, 555); ck_assert_int_eq(st.st_gid, 741); #endif res = stat(conn->request.u.shm.rb->shared_hdr->hdr_path, &st); ck_assert_int_eq(res, 0); ck_assert_int_eq(st.st_uid, 555); ck_assert_int_eq(st.st_gid, 741); qb_ipcc_disconnect(conn); verify_graceful_stop(pid); } END_TEST START_TEST(test_ipc_disp_shm_native_prio_dlock) { pid_t server_pid, alphaclient_pid; struct dispatch_data data; qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); /* this is to demonstrate that native event loop can deal even with "extreme" priority disproportions */ global_loop_prio = QB_LOOP_LOW; multiple_connections = QB_TRUE; recv_timeout = -1; server_pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(server_pid != -1); data = (struct dispatch_data){.server_pid = server_pid, .msg_type = IPC_MSG_REQ_SELF_FEED, .repetitions = 1}; alphaclient_pid = run_function_in_new_process("alphaclient", client_dispatch, (void *) &data); ck_assert(alphaclient_pid != -1); //sleep(1); sched_yield(); data.repetitions = 0; client_dispatch(connected_signaller, NULL, (void *) &data); verify_graceful_stop(server_pid); multiple_connections = QB_FALSE; qb_leave(); } END_TEST #if HAVE_GLIB START_TEST(test_ipc_disp_shm_glib_prio_dlock) { pid_t server_pid, alphaclient_pid; struct dispatch_data data; qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); global_use_glib = QB_TRUE; /* this is to make the test pass at all, since GLib is strict on priorities -- QB_LOOP_MED or lower would fail for sure */ global_loop_prio = QB_LOOP_HIGH; multiple_connections = QB_TRUE; recv_timeout = -1; server_pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(server_pid != -1); data = (struct dispatch_data){.server_pid = server_pid, .msg_type = IPC_MSG_REQ_SELF_FEED, .repetitions = 1}; alphaclient_pid = run_function_in_new_process("alphaclient", client_dispatch, (void *) &data); ck_assert(alphaclient_pid != -1); //sleep(1); sched_yield(); data.repetitions = 0; client_dispatch(connected_signaller, NULL, (void *) &data); verify_graceful_stop(server_pid); multiple_connections = QB_FALSE; global_loop_prio = QB_LOOP_MED; global_use_glib = QB_FALSE; qb_leave(); } END_TEST #endif START_TEST(test_ipc_bulk_events_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_bulk_events(); qb_leave(); } END_TEST START_TEST(test_ipc_event_on_created_shm) { qb_enter(); send_event_on_created = QB_TRUE; ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_event_on_created(); qb_leave(); } END_TEST START_TEST(test_ipc_server_fail_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_server_fail(); qb_leave(); } END_TEST #ifdef HAVE_FAILURE_INJECTION START_TEST(test_ipcc_truncate_when_unlink_fails_shm) { char sock_file[PATH_MAX]; struct sockaddr_un socka; qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); sprintf(sock_file, "%s/%s", SOCKETDIR, ipc_name); sock_file[sizeof(socka.sun_path)] = '\0'; /* If there's an old socket left from a previous run this test will fail unexpectedly, so try to remove it first */ unlink(sock_file); _fi_unlink_inject_failure = QB_TRUE; test_ipc_server_fail(); _fi_unlink_inject_failure = QB_FALSE; unlink(sock_file); qb_leave(); } END_TEST #endif static void test_ipc_service_ref_count(void) { int32_t c = 0; int32_t j = 0; pid_t pid; uint32_t max_size = MAX_MSG_SIZE; reference_count_test = QB_TRUE; pid = run_function_in_new_process("server", run_ipc_server, NULL); ck_assert(pid != -1); do { conn = qb_ipcc_connect(ipc_name, max_size); if (conn == NULL) { j = waitpid(pid, NULL, WNOHANG); ck_assert_int_eq(j, 0); (void)poll(NULL, 0, 400); c++; } } while (conn == NULL && c < 5); ck_assert(conn != NULL); sleep(5); kill_server(pid); } START_TEST(test_ipc_service_ref_count_shm) { qb_enter(); ipc_type = QB_IPC_SHM; set_ipc_name(__func__); test_ipc_service_ref_count(); qb_leave(); } END_TEST START_TEST(test_ipc_service_ref_count_us) { qb_enter(); ipc_type = QB_IPC_SOCKET; set_ipc_name(__func__); test_ipc_service_ref_count(); qb_leave(); } END_TEST #if 0 static void test_max_dgram_size(void) { /* most implementations will not let you set a dgram buffer * of 1 million bytes. This test verifies that the we can detect * the max dgram buffersize regardless, and that the value we detect * is consistent. */ int32_t init; int32_t i; qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_REMOVE, QB_LOG_FILTER_FILE, "*", LOG_TRACE); init = qb_ipcc_verify_dgram_max_msg_size(1000000); ck_assert(init > 0); for (i = 0; i < 100; i++) { int try = qb_ipcc_verify_dgram_max_msg_size(1000000); #if 0 ck_assert_int_eq(init, try); #else /* extra troubleshooting, report also on i and errno variables; related: https://github.com/ClusterLabs/libqb/issues/234 */ if (init != try) { #ifdef ci_dump_shm_usage system("df -h | grep -e /shm >/tmp/_shm_usage"); #endif ck_abort_msg("Assertion 'init==try' failed:" " init==%#x, try==%#x, i=%d, errno=%d", init, try, i, errno); } #endif } qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE); } START_TEST(test_ipc_max_dgram_size) { qb_enter(); test_max_dgram_size(); qb_leave(); } END_TEST #endif static Suite * make_shm_suite(void) { TCase *tc; Suite *s = suite_create("shm"); add_tcase(s, tc, test_ipc_shm_connect_async, 7); add_tcase(s, tc, test_ipc_txrx_shm_getauth, 7); add_tcase(s, tc, test_ipc_txrx_shm_timeout, 28); add_tcase(s, tc, test_ipc_server_fail_shm, 7); add_tcase(s, tc, test_ipc_txrx_shm_block, 7); add_tcase(s, tc, test_ipc_txrx_shm_tmo, 7); add_tcase(s, tc, test_ipc_fc_shm, 7); add_tcase(s, tc, test_ipc_dispatch_shm, 15); add_tcase(s, tc, test_ipc_stress_test_shm, 15); add_tcase(s, tc, test_ipc_bulk_events_shm, 15); add_tcase(s, tc, test_ipc_exit_shm, 6); add_tcase(s, tc, test_ipc_event_on_created_shm, 9); add_tcase(s, tc, test_ipc_service_ref_count_shm, 9); add_tcase(s, tc, test_ipc_server_perms, 7); add_tcase(s, tc, test_ipc_stress_connections_shm, 3600 /* ? */); add_tcase(s, tc, test_ipc_disp_shm_native_prio_dlock, 15); #if HAVE_GLIB add_tcase(s, tc, test_ipc_disp_shm_glib_prio_dlock, 15); #endif #ifdef HAVE_FAILURE_INJECTION add_tcase(s, tc, test_ipcc_truncate_when_unlink_fails_shm, 8); #endif return s; } static Suite * make_soc_suite(void) { Suite *s = suite_create("socket"); TCase *tc; add_tcase(s, tc, test_ipc_us_connect_async, 7); add_tcase(s, tc, test_ipc_txrx_us_getauth, 7); add_tcase(s, tc, test_ipc_txrx_us_timeout, 28); /* Commented out for the moment as space in /dev/shm on the CI machines causes random failures */ /* add_tcase(s, tc, test_ipc_max_dgram_size, 30); */ add_tcase(s, tc, test_ipc_server_fail_soc, 7); add_tcase(s, tc, test_ipc_txrx_us_block, 7); add_tcase(s, tc, test_ipc_txrx_us_tmo, 7); add_tcase(s, tc, test_ipc_fc_us, 7); add_tcase(s, tc, test_ipc_exit_us, 6); add_tcase(s, tc, test_ipc_dispatch_us, 15); #ifndef __clang__ /* see variable length array in structure' at the top */ add_tcase(s, tc, test_ipc_stress_test_us, 58); #endif add_tcase(s, tc, test_ipc_bulk_events_us, 15); add_tcase(s, tc, test_ipc_event_on_created_us, 9); add_tcase(s, tc, test_ipc_disconnect_after_created_us, 9); add_tcase(s, tc, test_ipc_service_ref_count_us, 9); add_tcase(s, tc, test_ipc_stress_connections_us, 3600 /* ? */); add_tcase(s, tc, test_ipc_us_native_prio_dlock, 15); #if HAVE_GLIB add_tcase(s, tc, test_ipc_us_glib_prio_dlock, 15); #endif return s; } int32_t main(void) { int32_t number_failed; SRunner *sr; Suite *s; int32_t do_shm_tests = QB_TRUE; set_ipc_name("ipc_test"); #ifdef DISABLE_IPC_SHM do_shm_tests = QB_FALSE; #endif /* DISABLE_IPC_SHM */ s = make_soc_suite(); sr = srunner_create(s); if (do_shm_tests) { srunner_add_suite(sr, make_shm_suite()); } qb_log_init("check", LOG_USER, LOG_EMERG); atexit(qb_log_fini); qb_log_ctl(QB_LOG_SYSLOG, QB_LOG_CONF_ENABLED, QB_FALSE); qb_log_filter_ctl(QB_LOG_STDERR, QB_LOG_FILTER_ADD, QB_LOG_FILTER_FILE, "*", LOG_TRACE); qb_log_ctl(QB_LOG_STDERR, QB_LOG_CONF_ENABLED, QB_TRUE); qb_log_format_set(QB_LOG_STDERR, "lib/%f|%l| %b"); srunner_run_all(sr, CK_VERBOSE); number_failed = srunner_ntests_failed(sr); srunner_free(sr); return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/tests/start.test b/tests/start.test index a02357a..3b270d7 100755 --- a/tests/start.test +++ b/tests/start.test @@ -1,15 +1,15 @@ #!/bin/sh # # Generate a unique(ish) name for the IPCs we will use in the tests and # save it in a file for all of the tests to use. This way we know for sure # which sockets are our and which we can ignore. # The test programs all add "qb-test--" to the front of this. # testname=$(echo `uuidgen | sed -e "s#-.*##g"`-`date +%s`) -echo -n $testname > ipc-test-name +echo $testname > ipc-test-name testname=$(echo `uuidgen | sed -e "s#-.*##g"`-`date +%s`) -echo -n $testname > ipc-test-name-sock +echo $testname > ipc-test-name-sock mkdir -p $SOCKETDIR