On the Cygwin 3.3.6 machine at GitHub I see a test failure:

FAIL: test-supersede
====================

../../gltests/test-supersede-open.h:109: assertion 'fd >= 0' failed
Stack trace:
0x4049fd print_stack_trace
        ../../gllib/abort-debug.c:40
0x4049fd _gl_pre_abort
        ../../gllib/abort-debug.c:81
0x4024d6 test_open_supersede
        ../../gltests/test-supersede-open.h:109
0x41d77b main
        ../../gltests/test-supersede.c:53

According to the line numbers, the problem is with the /dev/null file name.

Here are some traces:
open_supersede(true,false)1: filename=/dev/null, flags=0x10402, mode=0666
open_supersede(true,false)1: fd = -1, errno = 17
canonicalize_filename_mode name=/dev/null : readlink: rname=/dev -> -1
canonicalize_filename_mode name=/dev/null : readlink: rname=/dev/null -> 12     
      SHOULD BE -1
canonicalize_filename_mode name=\Device\Null : readlink: rname=/Device -> -1
canonicalize_filename_mode name=\Device\Null : failing
open_supersede(true,false): canon_filename = (null)
../../gltests/test-supersede-open.h:109: assertion 'fd >= 0' failed
../../gltests/test-supersede-open.h:110: assertion 'write (fd, "Foobar\n", 7) 
== 7' failed

The problem is that readlink of /dev/null returns \Device\Null, which is not
a usable file name in POSIX (Cygwin's own file system reports that '/Device'
does not exist).

So, to protect against this misbehaviour, I'm adding two unit tests, and as
expected they fail:


FAIL: test-canonicalize
=======================

../../gltests/test-canonicalize.c:430: assertion 'result2 != NULL' failed
FAIL test-canonicalize.exe (exit status: 139)

FAIL: test-readlink
===================

../../gltests/test-readlink.h:120: assertion 'result == -1 || buf[0] != '\\'' 
failed
FAIL test-readlink.exe (exit status: 1)


With a workaround in the 'readlink' module, these two tests now succeed.


2024-06-03  Bruno Haible  <br...@clisp.org>

        readlink: Work around a Cygwin 3.3.6 bug.
        * m4/readlink.m4 (gl_FUNC_READLINK): Set REPLACE_READLINK to 1 on 
Cygwin.
        * lib/readlink.c (rpl_readlink): On Cygwin, for /dev/* files, don't
        return results that start with a backslash.
        * tests/test-readlink.h (test_readlink): Add a test of /dev/null.
        * tests/test-canonicalize-lgpl.c (main): Likewise.
        * tests/test-canonicalize.c (main): Likewise.
        * doc/posix-functions/readlink.texi: Mention the Cygwin bug.

diff --git a/doc/posix-functions/readlink.texi 
b/doc/posix-functions/readlink.texi
index 31769d5598..8fde7f58a7 100644
--- a/doc/posix-functions/readlink.texi
+++ b/doc/posix-functions/readlink.texi
@@ -18,6 +18,10 @@
 On some platforms, this function returns @code{int} instead of
 @code{ssize_t}:
 glibc 2.4, FreeBSD 6.0, OpenBSD 6.7, Cygwin 1.5.x, AIX 7.1.
+@item
+For the file name @file{/dev/null}, this function returns @file{\Device\Null},
+which is unusable, on some platforms:
+Cygwin 3.3.6.
 @end itemize
 
 Portability problems mostly fixed by Gnulib:
diff --git a/lib/readlink.c b/lib/readlink.c
index a5369fa977..f4af30ebc4 100644
--- a/lib/readlink.c
+++ b/lib/readlink.c
@@ -98,6 +98,16 @@ rpl_readlink (char const *file, char *buf, size_t bufsize)
     }
 # endif
 
+# if defined __CYGWIN__
+  /* On Cygwin 3.3.6, readlink("/dev/null") returns "\\Device\\Null", which
+     is unusable.  Better fail with EINVAL.  */
+  if (r > 0 && strncmp (file, "/dev/", 5) == 0 && buf[0] == '\\')
+    {
+      errno = EINVAL;
+      return -1;
+    }
+# endif
+
   return r;
 }
 
diff --git a/m4/readlink.m4 b/m4/readlink.m4
index bc9a5deb06..7ebdb6ca14 100644
--- a/m4/readlink.m4
+++ b/m4/readlink.m4
@@ -1,5 +1,5 @@
 # readlink.m4
-# serial 17
+# serial 18
 dnl Copyright (C) 2003, 2007, 2009-2024 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -8,7 +8,7 @@
 AC_DEFUN([gl_FUNC_READLINK],
 [
   AC_REQUIRE([gl_UNISTD_H_DEFAULTS])
-  AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles
+  AC_REQUIRE([AC_CANONICAL_HOST])
   AC_CHECK_FUNCS_ONCE([readlink])
   if test $ac_cv_func_readlink = no; then
     HAVE_READLINK=0
@@ -18,9 +18,13 @@ AC_DEFUN([gl_FUNC_READLINK]
       [AC_COMPILE_IFELSE(
          [AC_LANG_PROGRAM(
            [[#include <unistd.h>
-      /* Cause compilation failure if original declaration has wrong type.  */
-      ssize_t readlink (const char *, char *, size_t);]])],
+             /* Cause compilation failure if original declaration has
+                wrong type.  */
+             ssize_t readlink (const char *, char *, size_t);
+           ]])
+         ],
          [gl_cv_decl_readlink_works=yes], [gl_cv_decl_readlink_works=no])])
+
     dnl Solaris 9 ignores trailing slash.
     dnl FreeBSD 7.2 dereferences only one level of links with trailing slash.
     AC_CACHE_CHECK([whether readlink handles trailing slash correctly],
@@ -31,8 +35,11 @@ AC_DEFUN([gl_FUNC_READLINK]
        AC_RUN_IFELSE(
          [AC_LANG_PROGRAM(
            [[#include <unistd.h>
-]], [[char buf[20];
-      return readlink ("conftest.lnk2/", buf, sizeof buf) != -1;]])],
+           ]],
+           [[char buf[20];
+             return readlink ("conftest.lnk2/", buf, sizeof buf) != -1;
+           ]])
+         ],
          [gl_cv_func_readlink_trailing_slash=yes],
          [gl_cv_func_readlink_trailing_slash=no],
          [case "$host_os" in
@@ -71,8 +78,11 @@ AC_DEFUN([gl_FUNC_READLINK]
        AC_RUN_IFELSE(
          [AC_LANG_PROGRAM(
            [[#include <unistd.h>
-]], [[char c;
-      return readlink ("conftest.link", &c, 1) != 1;]])],
+           ]],
+           [[char c;
+             return readlink ("conftest.link", &c, 1) != 1;
+           ]])
+         ],
          [gl_cv_func_readlink_truncate=yes],
          [gl_cv_func_readlink_truncate=no],
          [case "$host_os" in
@@ -103,6 +113,14 @@ AC_DEFUN([gl_FUNC_READLINK]
         REPLACE_READLINK=1
         ;;
     esac
+
+    dnl On Cygwin 3.3.6, readlink("/dev/null") returns "\\Device\\Null", which
+    dnl is unusable.
+    case "$host_os" in
+      cygwin*)
+        REPLACE_READLINK=1
+        ;;
+    esac
   fi
 ])
 
diff --git a/tests/test-canonicalize-lgpl.c b/tests/test-canonicalize-lgpl.c
index 769f7c3968..923f0fb46c 100644
--- a/tests/test-canonicalize-lgpl.c
+++ b/tests/test-canonicalize-lgpl.c
@@ -283,6 +283,13 @@ main (void)
     free (result2);
   }
 
+#if !(defined _WIN32 && !defined __CYGWIN__)
+  /* Check a device file.  */
+  {
+    char *result = canonicalize_file_name ("/dev/null");
+    ASSERT (result != NULL);
+  }
+#endif
 
   /* Cleanup.  */
   ASSERT (remove (BASE "/droot") == 0);
diff --git a/tests/test-canonicalize.c b/tests/test-canonicalize.c
index 0224c2bbcf..71177681ae 100644
--- a/tests/test-canonicalize.c
+++ b/tests/test-canonicalize.c
@@ -421,6 +421,17 @@ main (void)
     free (result4);
   }
 
+#if !(defined _WIN32 && !defined __CYGWIN__)
+  /* Check a device file.  */
+  {
+    char *result1 = canonicalize_file_name ("/dev/null");
+    char *result2 = canonicalize_filename_mode ("/dev/null", CAN_ALL_BUT_LAST);
+    ASSERT (result1 != NULL);
+    ASSERT (result2 != NULL);
+    ASSERT (strcmp (result1, result2) == 0);
+  }
+#endif
+
   /* Cleanup.  */
   ASSERT (remove (BASE "/droot") == 0);
   ASSERT (remove (BASE "/d/1") == 0);
diff --git a/tests/test-readlink.h b/tests/test-readlink.h
index c04ae311e2..d547bec959 100644
--- a/tests/test-readlink.h
+++ b/tests/test-readlink.h
@@ -113,6 +113,12 @@ test_readlink (ssize_t (*func) (char const *, char *, 
size_t), bool print)
        either left alone, or NUL-terminated.  */
     ASSERT (buf[len] == '\0' || buf[len] == (char) 0xff);
   }
+  {
+    /* On Cygwin 3.3.6, readlink("/dev/null") returns "\\Device\\Null", which
+       is unusable.  Verify that gnulib works around this nonsense.  */
+    ssize_t result = func ("/dev/null", buf, sizeof buf);
+    ASSERT (result == -1 || buf[0] != '\\');
+  }
   ASSERT (rmdir (BASE "dir") == 0);
   ASSERT (unlink (BASE "link") == 0);
 




Reply via email to