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);