Package: thttpd
Version: 2.21b-11
Tags: patch

To reproduce the bug:
1) start thttpd daemon (start-up options don't matter);
2) set the system clock 10 minutes back;
3) do nothing for about 6 minutes;
4) thttpd will terminate without a log message.

The bug appears in early versions as well:
http://www.mail-archive.com/php-gene...@lists.php.net/msg174036.html


Reasons.

The software utilizes system time to implement a number of internal timers for:
- internal watchdog;
- periodic file mmap cache expiration check;
- periodic idle connections closing;
- periodic average rate updates for throttles;
- periodic statistics displaying;
- connections "wake-up" (recovery);
- lingering connections closing;
- CGI execution time limit implementation (CGI_TIMELIMIT).

So the problem is a scheduled time is no longer valid after system time changing. For example, current time is 15:00 and thttpd schedules some timer to expire in 5 minutes i.e. at 15:05, then system time changes (DST, manual adjusting by user / NTP cron script). As a result, one of the following happens:
1) all active timers expire too late, if system time went backwards;
2) all active timers expire too early (even immediately), if system time went forward.

In the first case, if a watchdog handler is not called within appropriate period, the program calls abort() (thttpd.c:handle_alrm()) to go out of hang state (as it's supposed to happen). Other negative consequences (normal operation interruption) are also possible.


Solution.

The patch attached checks for MONOTONIC clock availability and uses it for timers expiration evaluation instead of system time. This includes both compile-time check (for the clock access function availability) and run-time check (to ensure that monotonic clock time is fetchable).

Notes: an ideal solution should completely re-write thttpd to POSIX timers.


Best regards,
Mikhail Zolotaryov

diff -Nru thttpd-2.25b.orig/configure thttpd-2.25b/configure
--- thttpd-2.25b.orig/configure 2003-12-25 20:44:33.000000000 +0200
+++ thttpd-2.25b/configure      2010-07-08 00:07:24.000000000 +0300
@@ -2299,13 +2299,61 @@
        ;;
 esac
 
+echo $ac_n "checking for clock_gettime in -lrt""... $ac_c" 1>&6
+echo "configure:2304: checking for clock_gettime in -lrt" >&5
+ac_lib_var=`echo rt'_'clock_gettime | sed 'y%./+-%__p_%'`
+if eval "test \"`echo '$''{'ac_cv_lib_$ac_lib_var'+set}'`\" = set"; then
+  echo $ac_n "(cached) $ac_c" 1>&6
+else
+  ac_save_LIBS="$LIBS"
+LIBS="-lrt  $LIBS"
+cat > conftest.$ac_ext <<EOF
+#line 2312 "configure"
+#include "confdefs.h"
+/* Override any gcc2 internal prototype to avoid an error.  */
+/* We use char because int might match the return type of a gcc2
+    builtin and then its argument prototype would still apply.  */
+char clock_gettime();
+
+int main() {
+clock_gettime()
+; return 0; }
+EOF
+if { (eval echo configure:2323: \"$ac_link\") 1>&5; (eval $ac_link) 2>&5; } && 
test -s conftest${ac_exeext}; then
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=yes"
+else
+  echo "configure: failed program was:" >&5
+  cat conftest.$ac_ext >&5
+  rm -rf conftest*
+  eval "ac_cv_lib_$ac_lib_var=no"
+fi
+rm -f conftest*
+LIBS="$ac_save_LIBS"
+
+fi
+if eval "test \"`echo '$ac_cv_lib_'$ac_lib_var`\" = yes"; then
+  echo "$ac_t""yes" 1>&6
+    ac_tr_lib=HAVE_LIB`echo rt | sed -e 's/[^a-zA-Z0-9_]/_/g' \
+    -e 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/'`
+  cat >> confdefs.h <<EOF
+#define $ac_tr_lib 1
+EOF
+
+  LIBS="-lrt $LIBS"
+
+else
+  echo "$ac_t""no" 1>&6
+fi
+
+
 echo $ac_n "checking if struct tm has tm_gmtoff member""... $ac_c" 1>&6
-echo "configure:2304: checking if struct tm has tm_gmtoff member" >&5
+echo "configure:2352: checking if struct tm has tm_gmtoff member" >&5
     if eval "test \"`echo '$''{'ac_cv_acme_tm_has_tm_gmtoff'+set}'`\" = set"; 
then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 2309 "configure"
+#line 2357 "configure"
 #include "confdefs.h"
 
 #      include <sys/types.h>
@@ -2314,7 +2362,7 @@
 u_int i = sizeof(((struct tm *)0)->tm_gmtoff)
 ; return 0; }
 EOF
-if { (eval echo configure:2318: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
+if { (eval echo configure:2366: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
   rm -rf conftest*
   ac_cv_acme_tm_has_tm_gmtoff=yes
 else
@@ -2334,12 +2382,12 @@
 
     fi
 echo $ac_n "checking if int64_t exists""... $ac_c" 1>&6
-echo "configure:2338: checking if int64_t exists" >&5
+echo "configure:2386: checking if int64_t exists" >&5
     if eval "test \"`echo '$''{'ac_cv_acme_int64_t'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 2343 "configure"
+#line 2391 "configure"
 #include "confdefs.h"
 
 #      include <sys/types.h>
@@ -2347,7 +2395,7 @@
 int64_t i64
 ; return 0; }
 EOF
-if { (eval echo configure:2351: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
+if { (eval echo configure:2399: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
   rm -rf conftest*
   ac_cv_acme_int64_t=yes
 else
@@ -2367,12 +2415,12 @@
 
     fi
 echo $ac_n "checking if socklen_t exists""... $ac_c" 1>&6
-echo "configure:2371: checking if socklen_t exists" >&5
+echo "configure:2419: checking if socklen_t exists" >&5
     if eval "test \"`echo '$''{'ac_cv_acme_socklen_t'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
 else
   cat > conftest.$ac_ext <<EOF
-#line 2376 "configure"
+#line 2424 "configure"
 #include "confdefs.h"
 
 #      include <sys/types.h>
@@ -2381,7 +2429,7 @@
 socklen_t slen
 ; return 0; }
 EOF
-if { (eval echo configure:2385: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
+if { (eval echo configure:2433: \"$ac_compile\") 1>&5; (eval $ac_compile) 
2>&5; }; then
   rm -rf conftest*
   ac_cv_acme_socklen_t=yes
 else
@@ -2402,7 +2450,7 @@
     fi
 
 echo $ac_n "checking whether ${MAKE-make} sets \${MAKE}""... $ac_c" 1>&6
-echo "configure:2406: checking whether ${MAKE-make} sets \${MAKE}" >&5
+echo "configure:2454: checking whether ${MAKE-make} sets \${MAKE}" >&5
 set dummy ${MAKE-make}; ac_make=`echo "$2" | sed 'y%./+-%__p_%'`
 if eval "test \"`echo '$''{'ac_cv_prog_make_${ac_make}_set'+set}'`\" = set"; 
then
   echo $ac_n "(cached) $ac_c" 1>&6
@@ -2440,7 +2488,7 @@
 # SVR4 /usr/ucb/install, which tries to use the nonexistent group "staff"
 # ./install, which can be erroneously created by make from ./install.sh.
 echo $ac_n "checking for a BSD compatible install""... $ac_c" 1>&6
-echo "configure:2444: checking for a BSD compatible install" >&5
+echo "configure:2492: checking for a BSD compatible install" >&5
 if test -z "$INSTALL"; then
 if eval "test \"`echo '$''{'ac_cv_path_install'+set}'`\" = set"; then
   echo $ac_n "(cached) $ac_c" 1>&6
diff -Nru thttpd-2.25b.orig/configure.in thttpd-2.25b/configure.in
--- thttpd-2.25b.orig/configure.in      2003-12-25 20:41:13.000000000 +0200
+++ thttpd-2.25b/configure.in   2010-07-08 00:04:25.000000000 +0300
@@ -123,6 +123,8 @@
        ;;
 esac
 
+AC_CHECK_LIB(rt, clock_gettime)
+
 AC_ACME_TM_GMTOFF
 AC_ACME_INT64T
 AC_ACME_SOCKLENT
diff -Nru thttpd-2.25b.orig/thttpd.c thttpd-2.25b/thttpd.c
--- thttpd-2.25b.orig/thttpd.c  2003-12-25 21:06:52.000000000 +0200
+++ thttpd-2.25b/thttpd.c       2010-07-08 00:41:28.000000000 +0300
@@ -742,7 +742,7 @@
        }
 
     /* Main loop. */
-    (void) gettimeofday( &tv, (struct timezone*) 0 );
+    tmr_prepare_timeval( &tv );
     while ( ( ! terminate ) || num_connects > 0 )
        {
        /* Do we need to re-open the log file? */
@@ -761,7 +761,7 @@
            syslog( LOG_ERR, "fdwatch - %m" );
            exit( 1 );
            }
-       (void) gettimeofday( &tv, (struct timezone*) 0 );
+       tmr_prepare_timeval( &tv );
 
        if ( num_ready == 0 )
            {
diff -Nru thttpd-2.25b.orig/timers.c thttpd-2.25b/timers.c
--- thttpd-2.25b.orig/timers.c  2002-08-22 04:04:12.000000000 +0300
+++ thttpd-2.25b/timers.c       2010-07-08 11:47:03.000000000 +0300
@@ -41,7 +41,13 @@
 
 ClientData JunkClientData;
 
-
+#undef HAVE_LIBRT_MONO
+#if defined(HAVE_LIBRT) && defined(CLOCK_MONOTONIC)
+#define HAVE_LIBRT_MONO
+#include <time.h>
+static int use_monotonic = 0;          /* monotonic clock runtime availability 
flag */
+static struct timeval tv_diff;         /* system time - monotonic difference 
at start */
+#endif
 
 static unsigned int
 hash( Timer* t )
@@ -145,6 +151,26 @@
        timers[h] = (Timer*) 0;
     free_timers = (Timer*) 0;
     alloc_count = active_count = free_count = 0;
+
+    /* Check for monotonic clock availability */
+#ifdef HAVE_LIBRT_MONO
+    struct timespec ts;
+    struct timeval tv_start, tv;
+    
+    /* Try to get monotonic clock time */
+    if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
+       use_monotonic = 1;
+
+       /* Get current system time */
+       (void) gettimeofday( &tv_start , (struct timezone*) 0 );
+       tv.tv_sec = ts.tv_sec;
+       tv.tv_usec = ts.tv_nsec / 1000L;
+       /* Calculate and save the difference: tv_start is since the Epoch, so 
tv_start > ts
+           tv_diff = tv_start - tv     */
+       timersub( &tv_start, &tv, &tv_diff );
+    }
+#endif
+
     }
 
 
@@ -176,7 +202,7 @@
     if ( nowP != (struct timeval*) 0 )
        t->time = *nowP;
     else
-       (void) gettimeofday( &t->time, (struct timezone*) 0 );
+       tmr_prepare_timeval( &t->time );
     t->time.tv_sec += msecs / 1000L;
     t->time.tv_usec += ( msecs % 1000L ) * 1000L;
     if ( t->time.tv_usec >= 1000000L )
@@ -349,3 +375,27 @@
     if ( active_count + free_count != alloc_count )
        syslog( LOG_ERR, "timer counts don't add up!" );
     }
+
+/* Fill timeval structure for further usage by the package. */
+void
+tmr_prepare_timeval( struct timeval *tv )
+{
+#ifdef HAVE_LIBRT_MONO
+    struct timespec ts;
+    struct timeval tv0;
+
+    if (use_monotonic) {       /* use monotonic clock source ? */
+       if (clock_gettime(CLOCK_MONOTONIC,&ts) < 0) {
+           perror("clock_gettime"); return;
+       }
+       tv0.tv_sec = ts.tv_sec;
+       tv0.tv_usec = ts.tv_nsec / 1000L;
+       /* Return system time value like it was running accurately */
+       timeradd( &tv_diff, &tv0, tv );
+    } else {
+#endif
+       (void) gettimeofday( tv , (struct timezone*) 0 );
+#ifdef HAVE_LIBRT_MONO
+    }
+#endif
+}
diff -Nru thttpd-2.25b.orig/timers.h thttpd-2.25b/timers.h
--- thttpd-2.25b.orig/timers.h  2001-04-13 08:37:41.000000000 +0300
+++ thttpd-2.25b/timers.h       2010-07-08 00:09:15.000000000 +0300
@@ -106,4 +106,7 @@
 /* Generate debugging statistics syslog message. */
 extern void tmr_logstats( long secs );
 
+/* Fill timeval structure for further usage by the package. */
+extern void tmr_prepare_timeval( struct timeval *tv );
+
 #endif /* _TIMERS_H_ */

Reply via email to