[PATCH 1/1] v2: Implement Condclockwait and Test

Joel Sherrill joel at rtems.org
Sun Aug 22 16:44:09 UTC 2021


On Thu, Aug 19, 2021 at 11:20 AM Matt Joyce <mfjoyce2004 at gmail.com> wrote:
>
> Added implementation of the pthread_cond_clockwait()
> method to cpukit/posix/src/condclockwait.c. Additional
> logic added to condwaitsupp.c to implement new method.
> pthread_cond_clockwait() has been added to the Issue 8
> POSIX Standard.

"implement new" to "implement the new"
>
> psxcond03 test added to testsuites/psxtests to test the
> newly added method.
> ---
>  cpukit/include/rtems/posix/condimpl.h         |   7 +-
>  cpukit/posix/src/condclockwait.c              |  78 +++
>  cpukit/posix/src/condtimedwait.c              |   3 +-
>  cpukit/posix/src/condwait.c                   |   6 +-
>  cpukit/posix/src/condwaitsupp.c               |  75 ++-
>  spec/build/testsuites/psxtests/grp.yml        |   2 +
>  spec/build/testsuites/psxtests/psxcond03.yml  |  20 +
>  testsuites/psxtests/Makefile.am               |   9 +
>  testsuites/psxtests/configure.ac              |   1 +
>  testsuites/psxtests/psxcond03/init.c          | 531 ++++++++++++++++++
>  testsuites/psxtests/psxcond03/psxcond03.doc   |  44 ++
>  testsuites/psxtests/psxcond03/psxcond03.scn   |  67 +++
>  testsuites/psxtests/psxcond03/system.h        |  61 ++
>  .../psxhdrs/pthread/pthread_cond_clockwait.c  |   2 +-
>  14 files changed, 875 insertions(+), 31 deletions(-)
>  create mode 100644 cpukit/posix/src/condclockwait.c
>  create mode 100644 spec/build/testsuites/psxtests/psxcond03.yml
>  create mode 100644 testsuites/psxtests/psxcond03/init.c
>  create mode 100644 testsuites/psxtests/psxcond03/psxcond03.doc
>  create mode 100644 testsuites/psxtests/psxcond03/psxcond03.scn
>  create mode 100644 testsuites/psxtests/psxcond03/system.h
>
> diff --git a/cpukit/include/rtems/posix/condimpl.h b/cpukit/include/rtems/posix/condimpl.h
> index 66e09bf6d8..95839e17d5 100644
> --- a/cpukit/include/rtems/posix/condimpl.h
> +++ b/cpukit/include/rtems/posix/condimpl.h
> @@ -150,9 +150,10 @@ int _POSIX_Condition_variables_Signal_support(
>   * timed wait version of condition variable wait routines.
>   */
>  int _POSIX_Condition_variables_Wait_support(
> -  pthread_cond_t            *cond,
> -  pthread_mutex_t           *mutex,
> -  const struct timespec     *abstime
> +  pthread_cond_t                *restrict cond,
> +  pthread_mutex_t              *restrict mutex,
> +  clockid_t                           clock_id,
> +  const struct timespec       *restrict abstime
>  );
>
>  bool _POSIX_Condition_variables_Auto_initialization(
> diff --git a/cpukit/posix/src/condclockwait.c b/cpukit/posix/src/condclockwait.c
> new file mode 100644
> index 0000000000..c2f071a749
> --- /dev/null
> +++ b/cpukit/posix/src/condclockwait.c
> @@ -0,0 +1,78 @@
> +/**
> + * @file
> + *
> + * @ingroup POSIXAPI
> + *
> + * @brief Waiting on a Condition
> + */
> +
> +/*
> +* Copyright (C) 2021 Matthew Joyce
> +*
> +* Redistribution and use in source and binary forms, with or without
> +* modification, are permitted provided that the following conditions
> +* are met:
> +* 1. Redistributions of source code must retain the above copyright
> +*    notice, this list of conditions and the following disclaimer.
> +* 2. Redistributions in binary form must reproduce the above copyright
> +*    notice, this list of conditions and the following disclaimer in the
> +*    documentation and/or other materials provided with the distribution.
> +*
> +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> +* POSSIBILITY OF SUCH DAMAGE.
> +*/
> +
> +/* Defining to have access to function prototype in libc/include/pthread.h */
> +#define _GNU_SOURCE
> +
> +#ifdef HAVE_CONFIG_H
> +#include "config.h"
> +#endif
> +
> +#include <rtems/posix/condimpl.h>
> +#include <rtems/score/todimpl.h>
> +
> +/*
> + *  pthread_cond_clockwait() appears in the Issue 8 POSIX Standard
> + */
> +int pthread_cond_clockwait(
> +  pthread_cond_t             *restrict cond,
> +  pthread_mutex_t           *restrict mutex,
> +  clockid_t                        clock_id,
> +  const struct timespec    *restrict abstime
> +)
> +{
> +  /*
> +   * POSIX Issue 8 does not specify that EINVAL is returned when abstime
> +   * equals NULL.
> +   */
> +  if ( abstime == NULL ) {
> +    return EINVAL;
> +  }
> +
> +  /*
> +   * POSIX Issue 8 specifies that EINVAL is returned when the clock is not
> +   * supported.
> +   */
> +  if ( clock_id != CLOCK_REALTIME ) {
> +    if ( clock_id != CLOCK_MONOTONIC ) {
> +      return EINVAL;
> +    }
> +  }
> +
> +  return _POSIX_Condition_variables_Wait_support(
> +    cond,
> +    mutex,
> +    clock_id,
> +    abstime
> +  );
> +}
> diff --git a/cpukit/posix/src/condtimedwait.c b/cpukit/posix/src/condtimedwait.c
> index 0bc8bfc18e..9b4a9ab4a3 100644
> --- a/cpukit/posix/src/condtimedwait.c
> +++ b/cpukit/posix/src/condtimedwait.c
> @@ -36,8 +36,9 @@ int pthread_cond_timedwait(
>      return EINVAL; /* not specified */
>    }
>    return _POSIX_Condition_variables_Wait_support(
> -    cond,
> +    cond,
>      mutex,
> +    (int)NULL,

Do not cast NULL as an (int). Would be better to have a new constant in
cpukit/include/rtems/posix/condimpl.h for CLOCK_NOT_SPECIFIED with
a name more like the internal symbols. Set it to a value nowhere near
the real clock constants, maybe something like 0xdeadbeef or whatever.
Then use RTEMS_STATIC_ASSERT at the top of the waitsupp.c file to
ensure your constant is not equal to ANY of the POSIX defined ones.


>      abstime
>    );
>  }
> diff --git a/cpukit/posix/src/condwait.c b/cpukit/posix/src/condwait.c
> index 09431e216d..46e72a2579 100644
> --- a/cpukit/posix/src/condwait.c
> +++ b/cpukit/posix/src/condwait.c
> @@ -51,9 +51,5 @@ int pthread_cond_wait(
>    pthread_mutex_t    *mutex
>  )
>  {
> -  return _POSIX_Condition_variables_Wait_support(
> -    cond,
> -    mutex,
> -    NULL
> -  );
> +  return _POSIX_Condition_variables_Wait_support(cond, mutex,(int)NULL, NULL);

Use the new constant

>  }
> diff --git a/cpukit/posix/src/condwaitsupp.c b/cpukit/posix/src/condwaitsupp.c
> index ee2f8a0787..d11d1a34d4 100644
> --- a/cpukit/posix/src/condwaitsupp.c
> +++ b/cpukit/posix/src/condwaitsupp.c
> @@ -92,9 +92,10 @@ static void _POSIX_Condition_variables_Enqueue_with_timeout_realtime(
>  }
>
>  int _POSIX_Condition_variables_Wait_support(
> -  pthread_cond_t            *cond,
> -  pthread_mutex_t           *mutex,
> -  const struct timespec     *abstime
> +  pthread_cond_t             *restrict cond,
> +  pthread_mutex_t           *restrict mutex,
> +  clockid_t                        clock_id,
> +  const struct timespec    *restrict abstime
>  )
>  {
>    POSIX_Condition_variables_Control *the_cond;
> @@ -107,28 +108,66 @@ int _POSIX_Condition_variables_Wait_support(
>    POSIX_CONDITION_VARIABLES_VALIDATE_OBJECT( the_cond, flags );
>
>    _Thread_queue_Context_initialize( &queue_context );
> +
> +  /*
> +   * If there is a clock_id parameter, this is a call to
> +   * pthread_cond_clockwait. Set the clock according to this parameter.
> +   */
> +  if ( clock_id ) {
> +    _Thread_queue_Context_set_timeout_argument( &queue_context,
> +     abstime, true

This either does not need to wrap or needs to be formatted differently for
going over 80 columns.

> +    );
>
> -  if ( abstime != NULL ) {
> -    _Thread_queue_Context_set_timeout_argument( &queue_context, abstime, true );
> -
> -    if ( _POSIX_Condition_variables_Get_clock( flags ) == CLOCK_MONOTONIC ) {
> +    /*
> +     * We have already validated supported clocks in condclockwait.c.
> +     * if clock_id is not CLOCK_MONOTONIC, then it is CLOCK_REALTIME.
> +     */
> +    if ( clock_id == CLOCK_MONOTONIC ) {
>        _Thread_queue_Context_set_enqueue_callout(
>          &queue_context,
>          _POSIX_Condition_variables_Enqueue_with_timeout_monotonic
>        );
> -    } else {
> -      _Thread_queue_Context_set_enqueue_callout(
> +    }
> +
> +    else {
> +        _Thread_queue_Context_set_enqueue_callout(
>          &queue_context,
>          _POSIX_Condition_variables_Enqueue_with_timeout_realtime
>        );
>      }

You might be able to clean up the code here with something like setting
a local variable of this type to the callout method and then having one
call to the enqueue method. that passes in the variable.

  Thread_queue_Enqueue_callout  enqueue_callout;



> -  } else {
> -    _Thread_queue_Context_set_enqueue_callout(
> -      &queue_context,
> -      _POSIX_Condition_variables_Enqueue_no_timeout
> -    );
>    }
>
> +  /*
> +   * If there is no clock_id parameter, this is either a call to
> +   * cond_timedwait or cond_wait.
> +   */
> +  else {
> +
> +    /* If there is an abstime parameter, this is a call to cond_timedwait. */
> +    if ( abstime != NULL ) {
> +      _Thread_queue_Context_set_timeout_argument( &queue_context,
> +       abstime, true
> +      );
> +
> +      if ( _POSIX_Condition_variables_Get_clock( flags ) == CLOCK_MONOTONIC ) {
> +        _Thread_queue_Context_set_enqueue_callout(
> +          &queue_context,
> +          _POSIX_Condition_variables_Enqueue_with_timeout_monotonic
> +        );
> +      } else {
> +        _Thread_queue_Context_set_enqueue_callout(
> +          &queue_context,
> +          _POSIX_Condition_variables_Enqueue_with_timeout_realtime
> +        );
> +      }
> +    /* If there is no abstime parameter, this is a call to cond_wait. */
> +    } else {
> +      _Thread_queue_Context_set_enqueue_callout(
> +        &queue_context,
> +        _POSIX_Condition_variables_Enqueue_no_timeout
> +      );
> +    }
> +  }
>    executing = _POSIX_Condition_variables_Acquire( the_cond, &queue_context );
>
>    if (
> @@ -138,9 +177,7 @@ int _POSIX_Condition_variables_Wait_support(
>      _POSIX_Condition_variables_Release( the_cond, &queue_context );
>      return EINVAL;
>    }
> -
>    the_cond->mutex = mutex;
> -
>    _Thread_queue_Context_set_thread_state(
>      &queue_context,
>      STATES_WAITING_FOR_CONDITION_VARIABLE
> @@ -151,8 +188,8 @@ int _POSIX_Condition_variables_Wait_support(
>      executing,
>      &queue_context
>    );
> +
>    error = _POSIX_Get_error_after_wait( executing );
> -
>    /*
>     *  If the thread is interrupted, while in the thread queue, by
>     *  a POSIX signal, then pthread_cond_wait returns spuriously,
> @@ -160,15 +197,12 @@ int _POSIX_Condition_variables_Wait_support(
>     *  returns a success status, except for the fact that it was not
>     *  woken up a pthread_cond_signal() or a pthread_cond_broadcast().
>     */
> -
>    if ( error == EINTR ) {
>      error = 0;
>    }
> -
>    /*
>     *  When we get here the dispatch disable level is 0.
>     */
> -
>    if ( error != EPERM ) {
>      int mutex_error;
>
> @@ -178,6 +212,5 @@ int _POSIX_Condition_variables_Wait_support(
>        error = EINVAL;
>      }
>    }
> -
>    return error;
>  }
> diff --git a/spec/build/testsuites/psxtests/grp.yml b/spec/build/testsuites/psxtests/grp.yml
> index fb7ce465ae..dc55c05124 100644
> --- a/spec/build/testsuites/psxtests/grp.yml
> +++ b/spec/build/testsuites/psxtests/grp.yml
> @@ -87,6 +87,8 @@ links:
>    uid: psxcond01
>  - role: build-dependency
>    uid: psxcond02
> +- role: build-dependency
> +  uid: psxcond03
>  - role: build-dependency
>    uid: psxconfig01
>  - role: build-dependency
> diff --git a/spec/build/testsuites/psxtests/psxcond03.yml b/spec/build/testsuites/psxtests/psxcond03.yml
> new file mode 100644
> index 0000000000..c0d53eb04e
> --- /dev/null
> +++ b/spec/build/testsuites/psxtests/psxcond03.yml
> @@ -0,0 +1,20 @@
> +SPDX-License-Identifier: CC-BY-SA-4.0 OR BSD-2-Clause
> +build-type: test-program
> +cflags: []
> +copyrights:
> +- Copyright (C) 2020 embedded brains GmbH (http://www.embedded-brains.de)
> +cppflags: []
> +cxxflags: []
> +enabled-by: true
> +features: c cprogram
> +includes: []
> +ldflags: []
> +links: []
> +source:
> +- testsuites/psxtests/psxcond03/init.c
> +- cpukit/posix/src/condclockwait.c
> +stlib: []
> +target: testsuites/psxtests/psxcond03.exe
> +type: build
> +use-after: []
> +use-before: []
> \ No newline at end of file
> diff --git a/testsuites/psxtests/Makefile.am b/testsuites/psxtests/Makefile.am
> index a35f00b665..de1a87b05f 100755
> --- a/testsuites/psxtests/Makefile.am
> +++ b/testsuites/psxtests/Makefile.am
> @@ -349,6 +349,15 @@ psxcond02_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_FLAGS_psxcond02) \
>         $(support_includes) -I$(top_srcdir)/include
>  endif
>
> +if TEST_psxcond03
> +psx_tests += psxcond03
> +psx_screens += psxcond03/psxcond03.scn
> +psx_docs += psxcond03/psxcond03.doc
> +psxcond03_SOURCES = psxcond03/init.c psxcond03/system.h include/pmacros.h
> +psxcond03_CPPFLAGS = $(AM_CPPFLAGS) $(TEST_FLAGS_psxcond03) \
> +       $(support_includes) -I$(top_srcdir)/include
> +endif
> +
>  if TEST_psxconfig01
>  psx_tests += psxconfig01
>  psx_screens += psxconfig01/psxconfig01.scn
> diff --git a/testsuites/psxtests/configure.ac b/testsuites/psxtests/configure.ac
> index 3f95010cd3..d88f52a846 100644
> --- a/testsuites/psxtests/configure.ac
> +++ b/testsuites/psxtests/configure.ac
> @@ -79,6 +79,7 @@ RTEMS_TEST_CHECK([psxclockrealtime01])
>  RTEMS_TEST_CHECK([psxconcurrency01])
>  RTEMS_TEST_CHECK([psxcond01])
>  RTEMS_TEST_CHECK([psxcond02])
> +RTEMS_TEST_CHECK([psxcond03])
>  RTEMS_TEST_CHECK([psxconfig01])
>  RTEMS_TEST_CHECK([psxdevctl01])
>  RTEMS_TEST_CHECK([psxeintr_join])
> diff --git a/testsuites/psxtests/psxcond03/init.c b/testsuites/psxtests/psxcond03/init.c
> new file mode 100644
> index 0000000000..bb1daf1b8b
> --- /dev/null
> +++ b/testsuites/psxtests/psxcond03/init.c
> @@ -0,0 +1,531 @@
> +/*
> +* Copyright (C) 2021 Matthew Joyce
> +*
> +* Redistribution and use in source and binary forms, with or without
> +* modification, are permitted provided that the following conditions
> +* are met:
> +* 1. Redistributions of source code must retain the above copyright
> +*    notice, this list of conditions and the following disclaimer.
> +* 2. Redistributions in binary form must reproduce the above copyright
> +*    notice, this list of conditions and the following disclaimer in the
> +*    documentation and/or other materials provided with the distribution.
> +*
> +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> +* POSSIBILITY OF SUCH DAMAGE.
> +*/
> +
> +/* Defining in order to access pthread_cond_clockwait in pthread.h */
> +#define _GNU_SOURCE
> +
> +#ifdef HAVE_CONFIG_H
> +#include "config.h"
> +#endif
> +
> +#define CONFIGURE_INIT
> +#include <pthread.h>
> +#include <sched.h>
> +#include <errno.h>
> +#include <rtems.h>
> +#include <tmacros.h>
> +#include "system.h"
> +
> +pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
> +pthread_cond_t bad_cond;
> +pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
> +pthread_mutex_t bad_mutex;
> +pthread_mutex_t new_mutex = PTHREAD_MUTEX_INITIALIZER;
> +clockid_t clock_id1 = CLOCK_MONOTONIC;
> +clockid_t clock_id2 = CLOCK_REALTIME;
> +clockid_t clock_id3 = CLOCK_MONOTONIC_RAW;
> +int count;
> +int eno;
> +int status;
> +const char rtems_test_name[] = "PSXCOND 3";
> +
> +void *POSIX_Init(
> +  void *argument
> +)
> +{
> +  TEST_BEGIN();
> +  empty_line();
> +  pthread_t t1;
> +  struct timespec abstime;
> +
> +  /* Expected pass: Clock Monotonic with sufficient abstime */
> +  printf("1st iteration Begin (Clock Monotonic)\n");
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "1st iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Clock Monotonic with insufficient abstime */
> +  printf( "2nd iteration Begin (Clock Monotonic)\n" );
> +  abstime.tv_sec = 0;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == ETIMEDOUT );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "2nd iteration END\n" );
> +  empty_line();
> +
> +  /* Expected pass: Clock Realtime with sufficient abstime */
> +  printf( "3rd iteration Begin (Clock Realtime)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec += 4;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "3rd iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Clock Realtime with insufficient abstime */
> +  printf( "4th iteration Begin (Clock Realtime)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec = 0;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == ETIMEDOUT );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "4th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Unsupported Clock */
> +  printf( "5th iteration Begin (Unsupported Clock)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id3, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == EINVAL );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "5th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Invalid Clock */
> +  printf( "6th iteration Begin (Invalid Clock)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, (int)NULL, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == EINVAL );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "6th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Invalid Clock */
> +  printf( "7th iteration Begin (Invalid Abstime)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &mutex, clock_id1, NULL );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == EINVAL );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "7th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Invalid Clock */
> +  printf( "8th iteration Begin (Invalid Cond)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( NULL, &mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == EINVAL );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "8th iteration END\n" );
> +  empty_line();
> +
> +    /* Expected fail: Invalid Mutex */
> +  printf( "9th iteration Begin (Invalid Mutex)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, NULL, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno != 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "9th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Uninitialized condition variable */
> +  printf( "10th iteration Begin (Uninitialized Condition Variable)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &bad_cond, &mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno != 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "10th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Uninitialized condition variable */
> +  printf( "11th iteration Begin (Uninitialized Mutex)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &bad_mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno != 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "11th iteration END\n" );
> +  empty_line();
> +
> +    /* Expected fail: Uninitialized condition variable */
> +  printf( "12th iteration Begin (Uninitialized Condition Variable)\n" );
> +  abstime.tv_sec = 2;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &bad_cond, &mutex, clock_id1, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno != 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "12th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected pass: Binding new mutex to condition variable */
> +  printf( "13th iteration Begin (New Mutex)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec += 4;
> +  abstime.tv_nsec = 1;
> +  status = pthread_create( &t1, NULL, thread_func2, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &new_mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "13th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected fail: Timeout (abstime < current time) */
> +  printf( "14th iteration Begin (abstime < current time)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec -= 1;
> +  abstime.tv_nsec -= 1;
> +  status = pthread_create( &t1, NULL, thread_func2, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &new_mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == ETIMEDOUT );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "14th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected Pass: Sufficient abstime to exceed sleep in thread_func3*/
> +  printf( "15th iteration Begin (sufficient abstime to exceed sleep)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec += 4;
> +  abstime.tv_nsec += 1;
> +  status = pthread_create( &t1, NULL, thread_func3, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &new_mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == 0 );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "15th iteration END\n" );
> +  empty_line();
> +
> +  /* Expected Fail: Insufficient abstime to exceed sleep in thread_func3*/
> +  printf( "16th iteration Begin (insufficient abstime to exceed sleep)\n" );
> +  status = clock_gettime( clock_id2, &abstime );
> +  rtems_test_assert( status == 0 );
> +  abstime.tv_sec += 2;
> +  abstime.tv_nsec += 1;
> +  status = pthread_create( &t1, NULL, thread_func3, NULL );
> +  rtems_test_assert( status == 0 );
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  while ( count == 0 ){
> +    eno = pthread_cond_clockwait( &cond, &new_mutex, clock_id2, &abstime );
> +    if ( eno != 0 ) {
> +      break;
> +    }
> +  }
> +
> +  count -= 1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  rtems_test_assert( eno == ETIMEDOUT );
> +  printf( "Return value from cond_clockwait is %d\n", eno );
> +  status = pthread_join( t1, NULL );
> +  rtems_test_assert( status == 0 );
> +  printf( "16th iteration END\n" );
> +  empty_line();
> +
> +  TEST_END();
> +  rtems_test_exit( 0 );
> +  return NULL;
> +}
> +
> +void *thread_func( void *arg )
> +{
> +  int status;
> +
> +  status = pthread_mutex_lock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  if (count == 0) {
> +    status = pthread_cond_signal( &cond );
> +    rtems_test_assert( status == 0 );
> +  }
> +
> +  count +=1;
> +  status = pthread_mutex_unlock( &mutex );
> +  rtems_test_assert( status == 0 );
> +  return NULL;
> +}
> +
> +void *thread_func2( void *arg )
> +{
> +  int status;
> +
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  if ( count == 0 ) {
> +    status = pthread_cond_signal( &cond );
> +    rtems_test_assert( status == 0 );
> +  }
> +
> +  count +=1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  return NULL;
> +}
> +
> +void *thread_func3( void *arg )
> +{
> +  int status;
> +
> +  status = pthread_mutex_lock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  /* Arbitrary sleep to test timeout functionality */
> +  sleep(3);
> +  if ( count == 0 ) {
> +    status = pthread_cond_signal( &cond );
> +    rtems_test_assert( status == 0 );
> +  }
> +
> +  count +=1;
> +  status = pthread_mutex_unlock( &new_mutex );
> +  rtems_test_assert( status == 0 );
> +  return NULL;
> +}

Still a lot of printing. Is comparing the screen needed to know it passed?

You should either delete the printfs of put them inside a debug macro
and turn them off by default.

> diff --git a/testsuites/psxtests/psxcond03/psxcond03.doc b/testsuites/psxtests/psxcond03/psxcond03.doc
> new file mode 100644
> index 0000000000..4e9b582189
> --- /dev/null
> +++ b/testsuites/psxtests/psxcond03/psxcond03.doc
> @@ -0,0 +1,44 @@
> +/*
> +* Copyright (C) 2021 Matthew Joyce
> +*
> +* Redistribution and use in source and binary forms, with or without
> +* modification, are permitted provided that the following conditions
> +* are met:
> +* 1. Redistributions of source code must retain the above copyright
> +*    notice, this list of conditions and the following disclaimer.
> +* 2. Redistributions in binary form must reproduce the above copyright
> +*    notice, this list of conditions and the following disclaimer in the
> +*    documentation and/or other materials provided with the distribution.
> +*
> +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> +* POSSIBILITY OF SUCH DAMAGE.
> +*/
> +
> +This file describes the directives and concepts tested by this test set.
> +
> +test set name:  psxcond03
> +
> +directives:
> +
> +  pthread_cond_clockwait
> +  pthread_create
> +  pthread_mutex_lock
> +  pthread_mutex_unlock
> +  pthread_cond_signal
> +  pthread_join
> +
> +concepts:
> +
> ++ Tests the newly-added Issue 8 POSIX Standard pthread_cond_clockwait method.
> +  Tests include valid/supported and invalid/unsupported clocks, sufficient and
> +  insufficient timeouts, and invalid or uninitialized condition/mutex
> +  parameters.
> diff --git a/testsuites/psxtests/psxcond03/psxcond03.scn b/testsuites/psxtests/psxcond03/psxcond03.scn
> new file mode 100644
> index 0000000000..e1792ca9cd
> --- /dev/null
> +++ b/testsuites/psxtests/psxcond03/psxcond03.scn
> @@ -0,0 +1,67 @@
> +*** BEGIN OF TEST PSXCOND 3 ***
> +
> +1st iteration Begin (Clock Monotonic)
> +Return value from cond_clockwait is 0
> +1st iteration END
> +
> +2nd iteration Begin (Clock Monotonic)
> +Return value from cond_clockwait is 116
> +2nd iteration END
> +
> +3rd iteration Begin (Clock Realtime)
> +Return value from cond_clockwait is 0
> +3rd iteration END
> +
> +4th iteration Begin (Clock Realtime)
> +Return value from cond_clockwait is 116
> +4th iteration END
> +
> +5th iteration Begin (Unsupported Clock)
> +Return value from cond_clockwait is 22
> +5th iteration END
> +
> +6th iteration Begin (Invalid Clock)
> +Return value from cond_clockwait is 22
> +6th iteration END
> +
> +7th iteration Begin (Invalid Abstime)
> +Return value from cond_clockwait is 22
> +7th iteration END
> +
> +8th iteration Begin (Invalid Cond)
> +Return value from cond_clockwait is 22
> +8th iteration END
> +
> +9th iteration Begin (Invalid Mutex)
> +Return value from cond_clockwait is 1
> +9th iteration END
> +
> +10th iteration Begin (Uninitialized Condition Variable)
> +Return value from cond_clockwait is 116
> +10th iteration END
> +
> +11th iteration Begin (Uninitialized Mutex)
> +Return value from cond_clockwait is 116
> +11th iteration END
> +
> +12th iteration Begin (Uninitialized Condition Variable)
> +Return value from cond_clockwait is 116
> +12th iteration END
> +
> +13th iteration Begin (New Mutex)
> +Return value from cond_clockwait is 0
> +13th iteration END
> +
> +14th iteration Begin (abstime < current time)
> +Return value from cond_clockwait is 116
> +14th iteration END
> +
> +15th iteration Begin (sufficient abstime to exceed sleep)
> +Return value from cond_clockwait is 0
> +15th iteration END
> +
> +16th iteration Begin (insufficient abstime to exceed sleep)
> +Return value from cond_clockwait is 116
> +16th iteration END
> +
> +*** END OF TEST PSXCOND 3 ***
> diff --git a/testsuites/psxtests/psxcond03/system.h b/testsuites/psxtests/psxcond03/system.h
> new file mode 100644
> index 0000000000..b9dcb832ad
> --- /dev/null
> +++ b/testsuites/psxtests/psxcond03/system.h
> @@ -0,0 +1,61 @@
> +/*
> +* Copyright (C) 2021 Matthew Joyce
> +*
> +* Redistribution and use in source and binary forms, with or without
> +* modification, are permitted provided that the following conditions
> +* are met:
> +* 1. Redistributions of source code must retain the above copyright
> +*    notice, this list of conditions and the following disclaimer.
> +* 2. Redistributions in binary form must reproduce the above copyright
> +*    notice, this list of conditions and the following disclaimer in the
> +*    documentation and/or other materials provided with the distribution.
> +*
> +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> +* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> +* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> +* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> +* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> +* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> +* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> +* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> +* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> +* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> +* POSSIBILITY OF SUCH DAMAGE.
> +*/
> +
> +/* functions */
> +
> +#include <pmacros.h>
> +#include <unistd.h>
> +#include <errno.h>
> +
> +void *POSIX_Init(
> +  void *argument
> +);
> +
> +void *thread_func(
> +  void *argument
> +);
> +
> +void *thread_func2(
> +  void *argument
> +);
> +
> +void *thread_func3(
> +  void *argument
> +);
> +
> +/* configuration information */
> +
> +#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER
> +#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
> +
> +#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION
> +
> +#define CONFIGURE_MAXIMUM_POSIX_THREADS              4
> +
> +#define CONFIGURE_POSIX_INIT_THREAD_TABLE
> +
> +#include <rtems/confdefs.h>
> +
> +/* end of include file */
> diff --git a/testsuites/psxtests/psxhdrs/pthread/pthread_cond_clockwait.c b/testsuites/psxtests/psxhdrs/pthread/pthread_cond_clockwait.c
> index 15485eb587..fe91b7954f 100644
> --- a/testsuites/psxtests/psxhdrs/pthread/pthread_cond_clockwait.c
> +++ b/testsuites/psxtests/psxhdrs/pthread/pthread_cond_clockwait.c
> @@ -49,7 +49,7 @@ int test( void )
>    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
>    clockid_t clock_id = CLOCK_REALTIME;
>    struct timespec abstime;
> -  abstime.tv_sec = 2;
> +  abstime.tv_sec = 1;
>    int result;
>
>    /* This method appeared in the Issue 8 POSIX Standard */
> --
> 2.31.1
>
> _______________________________________________
> devel mailing list
> devel at rtems.org
> http://lists.rtems.org/mailman/listinfo/devel


More information about the devel mailing list