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_ */