Collin Funk wrote:
> Unrelated to the original patch, but something I noticed...
>
> > +#if defined _WIN32 && !defined __CYGWIN__
> > + fd = open ("NUL", O_RDWR);
> > +#else
> > + fd = open ("/dev/null", O_RDWR);
> > +#endif
>
> Perhaps it is worth adding a macro for this file?
Good point. In fact, it's better to support "/dev/null" also on Windows:
* The main principle behind Gnulib is to present a POSIX/GNU façade
to applications on all platforms.
* File names are special, because on native Windows, the syntax of 'C:\'
is not compatible with POSIX. But "/dev/null" is used as an idiom,
not really as a file name. (No one cares about which directory
"/dev/null" sits in.)
* It is useful if scripts can use the command 'msgfmt -c -o /dev/null foo.po'
also on native Windows. No need to change the scripts.
(Similar to what bash does with /dev/fd/N, also on systems which don't
have /dev/fd/ natively.)
* Gnulib's open() function already supports "/dev/null" on native Windows.
For consistency, stat() should support it as well.
2025-06-13 Bruno Haible <[email protected]>
stat: Support the file name "/dev/null" on native Windows.
Reported by Collin Funk in
<https://lists.gnu.org/archive/html/bug-gnulib/2025-06/msg00119.html>.
* lib/stat.c (rpl_stat): On native Windows, map "/dev/null" to "NUL".
* tests/test-fstat.c (main): Test /dev/null also on native Windows.
* tests/test-stat.h (test_stat_func): Likewise.
* tests/test-lstat.h (test_lstat_func): Likewise.
* doc/posix-functions/stat.texi: Mention problem of null device name.
* doc/posix-functions/lstat.texi: Likewise.
diff --git a/doc/posix-functions/lstat.texi b/doc/posix-functions/lstat.texi
index 43728a5343..7bbc32a81e 100644
--- a/doc/posix-functions/lstat.texi
+++ b/doc/posix-functions/lstat.texi
@@ -33,6 +33,9 @@
offset from @code{tv_sec}. Solaris 11.4 is similar, except that
@code{tv_sec} might also be @minus{}1000000000.
@item
+On Windows platforms (excluding Cygwin), a different name has to be used
+for the null device, namely @code{"NUL"} instead of @code{"/dev/null"}.
+@item
On Windows platforms (excluding Cygwin), symlinks are not supported, so
@code{lstat} does not exist.
@end itemize
diff --git a/doc/posix-functions/stat.texi b/doc/posix-functions/stat.texi
index 6e0df396a5..80762977a8 100644
--- a/doc/posix-functions/stat.texi
+++ b/doc/posix-functions/stat.texi
@@ -45,6 +45,9 @@
@minus{}999999999..@minus{}1, representing a negative nanoseconds
offset from @code{tv_sec}. Solaris 11.4 is similar, except that
@code{tv_sec} might also be @minus{}1000000000.
+@item
+On Windows platforms (excluding Cygwin), a different name has to be used
+for the null device, namely @code{"NUL"} instead of @code{"/dev/null"}.
@end itemize
Portability problems not fixed by Gnulib:
diff --git a/lib/stat.c b/lib/stat.c
index ebed63e55a..6663783703 100644
--- a/lib/stat.c
+++ b/lib/stat.c
@@ -118,6 +118,10 @@ rpl_stat (char const *name, struct stat *buf)
around length limitations
<https://docs.microsoft.com/en-us/windows/desktop/FileIO/naming-a-file> ?
*/
+ /* To ease portability. Like in open.c. */
+ if (strcmp (name, "/dev/null") == 0)
+ name = "NUL";
+
/* POSIX
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>
specifies: "More than two leading <slash> characters shall be treated as
a single <slash> character." */
diff --git a/tests/test-fstat.c b/tests/test-fstat.c
index 7e65ee2f23..2fda69630a 100644
--- a/tests/test-fstat.c
+++ b/tests/test-fstat.c
@@ -52,11 +52,7 @@ main ()
int fd;
struct stat statbuf;
-#if defined _WIN32 && !defined __CYGWIN__
- fd = open ("NUL", O_RDWR);
-#else
fd = open ("/dev/null", O_RDWR);
-#endif
ASSERT (fstat (fd, &statbuf) == 0);
close (fd);
ASSERT (!S_ISREG (statbuf.st_mode));
diff --git a/tests/test-lstat.h b/tests/test-lstat.h
index bfd5b5526b..899cb26755 100644
--- a/tests/test-lstat.h
+++ b/tests/test-lstat.h
@@ -66,11 +66,7 @@ test_lstat_func (int (*func) (char const *, struct stat *),
bool print)
/* /dev/null is a character device.
Except on Solaris, where it is a symlink. */
-#if defined _WIN32 && !defined __CYGWIN__
- ASSERT (func ("NUL", &st1) == 0);
-#else
ASSERT (func ("/dev/null", &st1) == 0);
-#endif
ASSERT (!S_ISREG (st1.st_mode));
#if !defined __sun
ASSERT (S_ISCHR (st1.st_mode));
diff --git a/tests/test-stat.h b/tests/test-stat.h
index 3d2b912e8f..d055522f39 100644
--- a/tests/test-stat.h
+++ b/tests/test-stat.h
@@ -62,11 +62,7 @@ test_stat_func (int (*func) (char const *, struct stat *),
bool print)
ASSERT (errno == ENOTDIR);
/* /dev/null is a character device. */
-#if defined _WIN32 && !defined __CYGWIN__
- ASSERT (func ("NUL", &st1) == 0);
-#else
ASSERT (func ("/dev/null", &st1) == 0);
-#endif
ASSERT (!S_ISREG (st1.st_mode));
ASSERT (S_ISCHR (st1.st_mode));