nanosleep: work around cygwin bug
authorEric Blake <ebb9@byu.net>
Thu, 19 Nov 2009 03:10:42 +0000 (20:10 -0700)
committerEric Blake <ebb9@byu.net>
Fri, 20 Nov 2009 14:11:28 +0000 (07:11 -0700)
Cygwin 1.5.x mistakenly failed with EINVAL for a duration longer
than 49.7 days (2**32 milliseconds).  Meanwhile, the existing
code for HAVE_BUG_BIG_NANOSLEEP would infloop, instead of return
failure, for invalid arguments.

* lib/nanosleep.c (rpl_nanosleep) [HAVE_BUG_BIG_NANOSLEEP]:
Fix logic bug when nanosleep fails.  Work around cygwin 1.5.x
bug.
(getnow): Delete, not needed.
* m4/nanosleep.m4 (gl_FUNC_NANOSLEEP): No longer require
LIB_CLOCK_GETTIME.
* modules/nanosleep (Depends-on): Add intprops and verify.  Drop
clock-time, gettime.
* doc/posix-functions/nanosleep.texi (nanosleep): Document the
bug.
* modules/nanosleep-tests: New test.
* tests/test-nanosleep.c: New file.

Signed-off-by: Eric Blake <ebb9@byu.net>
ChangeLog
doc/posix-functions/nanosleep.texi
lib/nanosleep.c
m4/nanosleep.m4
modules/nanosleep
modules/nanosleep-tests [new file with mode: 0644]
tests/test-nanosleep.c [new file with mode: 0644]

index 23aa574..10f2c74 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,19 @@
 2009-11-20  Eric Blake  <ebb9@byu.net>
 
+       nanosleep: work around cygwin bug
+       * lib/nanosleep.c (rpl_nanosleep) [HAVE_BUG_BIG_NANOSLEEP]:
+       Fix logic bug when nanosleep fails.  Work around cygwin 1.5.x
+       bug.
+       (getnow): Delete, not needed.
+       * m4/nanosleep.m4 (gl_FUNC_NANOSLEEP): No longer require
+       LIB_CLOCK_GETTIME.
+       * modules/nanosleep (Depends-on): Add intprops and verify.  Drop
+       clock-time, gettime.
+       * doc/posix-functions/nanosleep.texi (nanosleep): Document the
+       bug.
+       * modules/nanosleep-tests: New test.
+       * tests/test-nanosleep.c: New file.
+
        sleep: work around cygwin bug
        * lib/sleep.c (rpl_sleep): Work around the bug.
        * m4/sleep.m4 (gl_FUNC_SLEEP): Detect the bug.
index a44ffa7..f4f0b56 100644 (file)
@@ -19,6 +19,9 @@ AIX 4.3.2.
 This function mishandles large arguments when interrupted by a signal on some
 platforms:
 Linux 64-bit, Solaris 64-bit.
+@item
+This function cannot sleep longer than 49.7 days on some platforms:
+Cygwin 1.5.x.
 @end itemize
 
 Portability problems not fixed by Gnulib:
index 7a7cc02..a9311f8 100644 (file)
@@ -1,7 +1,7 @@
 /* Provide a replacement for the POSIX nanosleep function.
 
-   Copyright (C) 1999, 2000, 2002, 2004, 2005, 2006, 2007, 2008 Free
-   Software Foundation, Inc.
+   Copyright (C) 1999, 2000, 2002, 2004, 2005, 2006, 2007, 2008, 2009
+   Free Software Foundation, Inc.
 
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
@@ -22,8 +22,9 @@
 
 #include <time.h>
 
+#include "intprops.h"
 #include "sig-handler.h"
-#include "timespec.h"
+#include "verify.h"
 
 #include <stdbool.h>
 #include <stdio.h>
@@ -42,56 +43,45 @@ enum { BILLION = 1000 * 1000 * 1000 };
 
 #if HAVE_BUG_BIG_NANOSLEEP
 
-static void
-getnow (struct timespec *t)
-{
-# if defined CLOCK_MONOTONIC && HAVE_CLOCK_GETTIME
-  if (clock_gettime (CLOCK_MONOTONIC, t) == 0)
-    return;
-# endif
-  gettime (t);
-}
-
 int
 rpl_nanosleep (const struct timespec *requested_delay,
               struct timespec *remaining_delay)
 {
   /* nanosleep mishandles large sleeps due to internal overflow
-     problems, so check that the proper amount of time has actually
-     elapsed.  */
-
-  struct timespec delay = *requested_delay;
-  struct timespec t0;
-  getnow (&t0);
-
-  for (;;)
+     problems.  The worst known case of this is cygwin 1.5.x, which
+     can't sleep more than 49.7 days (2**32 milliseconds).  Solve this
+     by breaking the sleep up into smaller chunks.  Verify that time_t
+     is large enough.  */
+  verify (TYPE_MAXIMUM (time_t) / 49 / 24 / 60 / 60);
+  const time_t limit = 49 * 24 * 60 * 60;
+  time_t seconds = requested_delay->tv_sec;
+  struct timespec intermediate;
+  intermediate.tv_nsec = 0;
+
+  while (limit < seconds)
     {
-      int r = nanosleep (&delay, remaining_delay);
-      if (r == 0)
-       {
-         time_t secs_sofar;
-         struct timespec now;
-         getnow (&now);
-
-         secs_sofar = now.tv_sec - t0.tv_sec;
-         if (requested_delay->tv_sec < secs_sofar)
-           return 0;
-         delay.tv_sec = requested_delay->tv_sec - secs_sofar;
-         delay.tv_nsec = requested_delay->tv_nsec - (now.tv_nsec - t0.tv_nsec);
-         if (delay.tv_nsec < 0)
-           {
-             if (delay.tv_sec == 0)
-               return 0;
-             delay.tv_nsec += BILLION;
-             delay.tv_sec--;
-           }
-         else if (BILLION <= delay.tv_nsec)
-           {
-             delay.tv_nsec -= BILLION;
-             delay.tv_sec++;
-           }
-       }
+      int result;
+      intermediate.tv_sec = limit;
+      result = nanosleep (&intermediate, remaining_delay);
+      seconds -= limit;
+      if (result)
+        {
+          if (remaining_delay)
+            {
+              remaining_delay->tv_sec += seconds;
+              remaining_delay->tv_nsec += requested_delay->tv_nsec;
+              if (BILLION <= requested_delay->tv_nsec)
+                {
+                  remaining_delay->tv_sec++;
+                  remaining_delay->tv_nsec -= BILLION;
+                }
+            }
+          return result;
+        }
     }
+  intermediate.tv_sec = seconds;
+  intermediate.tv_nsec = requested_delay->tv_nsec;
+  return nanosleep (&intermediate, remaining_delay);
 }
 
 #else
index d991b61..211b367 100644 (file)
@@ -1,4 +1,4 @@
-# serial 28
+# serial 29
 
 dnl From Jim Meyering.
 dnl Check for the nanosleep function.
@@ -17,7 +17,6 @@ AC_DEFUN([gl_FUNC_NANOSLEEP],
  AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
 
  AC_REQUIRE([gl_HEADER_TIME_H_DEFAULTS])
- AC_REQUIRE([gl_CLOCK_TIME])
  AC_CHECK_HEADERS_ONCE([sys/time.h])
 
  nanosleep_save_libs=$LIBS
@@ -103,12 +102,6 @@ AC_DEFUN([gl_FUNC_NANOSLEEP],
     if test "$gl_cv_func_nanosleep" = 'no (mishandles large arguments)'; then
       AC_DEFINE([HAVE_BUG_BIG_NANOSLEEP], [1],
        [Define to 1 if nanosleep mishandles large arguments.])
-      for ac_lib in $LIB_CLOCK_GETTIME; do
-       case " $LIB_NANOSLEEP " in
-       *" $ac_lib "*) ;;
-       *) LIB_NANOSLEEP="$LIB_NANOSLEEP $ac_lib";;
-       esac
-      done
     fi
     AC_LIBOBJ([nanosleep])
     gl_PREREQ_NANOSLEEP
index a652e53..d457b6f 100644 (file)
@@ -6,9 +6,8 @@ lib/nanosleep.c
 m4/nanosleep.m4
 
 Depends-on:
-clock-time
 extensions
-gettime
+intprops
 multiarch
 select
 sigaction
@@ -16,6 +15,7 @@ stdbool
 sys_select
 sys_time
 time
+verify
 
 configure.ac:
 gl_FUNC_NANOSLEEP
diff --git a/modules/nanosleep-tests b/modules/nanosleep-tests
new file mode 100644 (file)
index 0000000..67e6d4e
--- /dev/null
@@ -0,0 +1,12 @@
+Files:
+tests/test-nanosleep.c
+
+Depends-on:
+
+configure.ac:
+AC_CHECK_DECLS_ONCE([alarm])
+
+Makefile.am:
+TESTS += test-nanosleep
+check_PROGRAMS += test-nanosleep
+test_nanosleep_LDADD = $(LDADD) $(LIB_NANOSLEEP)
diff --git a/tests/test-nanosleep.c b/tests/test-nanosleep.c
new file mode 100644 (file)
index 0000000..eb4bef6
--- /dev/null
@@ -0,0 +1,94 @@
+/* Test of nanosleep() function.
+   Copyright (C) 2009 Free Software Foundation, Inc.
+
+   This program is free software: you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program 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 General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+/* Written by Eric Blake <ebb9@byu.net>, 2009.  */
+
+#include <config.h>
+
+#include <time.h>
+
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include "intprops.h"
+
+#define ASSERT(expr) \
+  do                                                                         \
+    {                                                                        \
+      if (!(expr))                                                           \
+        {                                                                    \
+          fprintf (stderr, "%s:%d: assertion failed\n", __FILE__, __LINE__); \
+          fflush (stderr);                                                   \
+          abort ();                                                          \
+        }                                                                    \
+    }                                                                        \
+  while (0)
+
+#if HAVE_DECL_ALARM
+static void
+handle_alarm (int sig)
+{
+  if (sig != SIGALRM)
+    _exit (1);
+}
+#endif
+
+int
+main()
+{
+  struct timespec ts;
+
+  ts.tv_sec = 1000;
+  ts.tv_nsec = -1;
+  errno = 0;
+  ASSERT (nanosleep (&ts, NULL) == -1);
+  ASSERT (errno == EINVAL);
+  ts.tv_nsec = 1000000000;
+  errno = 0;
+  ASSERT (nanosleep (&ts, NULL) == -1);
+  ASSERT (errno == EINVAL);
+
+  ts.tv_sec = 0;
+  ts.tv_nsec = 1;
+  ASSERT (nanosleep (&ts, &ts) == 0);
+  /* Remaining time is only defined on EINTR failure; but on success,
+     it is typically either 0 or unchanged from input.  At any rate,
+     it shouldn't be randomly changed to unrelated values.  */
+  ASSERT (ts.tv_sec == 0);
+  ASSERT (ts.tv_nsec == 0 || ts.tv_nsec == 1);
+  ts.tv_nsec = 0;
+  ASSERT (nanosleep (&ts, NULL) == 0);
+
+#if HAVE_DECL_ALARM
+  {
+    const time_t pentecost = 50 * 24 * 60 * 60; /* 50 days.  */
+    signal (SIGALRM, handle_alarm);
+    alarm (1);
+    ts.tv_sec = pentecost;
+    ts.tv_nsec = 999999999;
+    errno = 0;
+    ASSERT (nanosleep (&ts, &ts) == -1);
+    ASSERT (errno == EINTR);
+    ASSERT (pentecost - 10 < ts.tv_sec && ts.tv_sec <= pentecost);
+    ASSERT (0 <= ts.tv_nsec && ts.tv_nsec <= 999999999);
+  }
+#endif
+
+  return 0;
+}