+2025-05-26 Paul Eggert <eggert@cs.ucla.edu>
+
+ fcntl-h: support O_DIRECTORY
+ It is relatively easy to support O_DIRECTORY on platforms that
+ lack it, so let’s do that instead of having to work around bugs
+ like <https://bugs.gnu.org/78509#95>.
+ * lib/fcntl.in.h (O_DIRECTORY): Default to 0x20000000 not 0,
+ since Gnulib now supports it.
+ * lib/open.c, lib/openat.c (OPEN_TRAILING_SLASH_BUG):
+ Default to false, so that this can be used outside #if.
+ (open, openat): Add support for O_DIRECTORY on platforms that lack it.
+ If fstat fails, fail instead of assuming the file is a directory,
+ since failure can occur due to EOVERFLOW, etc.
+ Rearrange code to minimize differences between open.c and openat.c.
+ * m4/fcntl-o.m4 (gl_FCNTL_O_FLAGS): Also test O_DIRECTORY,
+ and define HAVE_WORKING_O_DIRECTORY if needed.
+ Prefer AS_CASE for Emacs’s benefit.
+ * m4/open.m4 (gl_FUNC_OPEN):
+ * m4/openat.m4 (gl_FUNC_OPENAT):
+ Require gl_FCNTL_O_FLAGS and replace the function
+ if O_DIRECTORY does not work.
+ * tests/test-open.h: Test O_DIRECTORY.
+
2025-05-26 Collin Funk <collin.funk1@gmail.com>
crypto/gc: Simplify the previous change.
@c See posix-headers/fcntl.texi for the list.
Mac OS X 10.6, FreeBSD 8.4, NetBSD 5.1, OpenBSD 4.9, Minix 3.1.8, AIX 7.1, HP-UX 11, Solaris 10, Cygwin 1.7.x, mingw, MSVC 14.
@item
+Some platforms do not support @code{O_DIRECTORY}:
+mingw, MSVC 14, glibc 2.0, OS X 10.9, FreeBSD 7.4, NetBSD 4.0.1, OpenBSD 4.9,
+AIX 7.1, HP-UX 11, Solaris 10.
+@item
On platforms where @code{off_t} is a 32-bit type, @code{open} may not work
correctly with files 2 GiB and larger. @xref{Large File Support}.
@item
Some platforms do not support @code{O_CLOEXEC}:
AIX 7.1, Solaris 10.
@item
+Some platforms do not support @code{O_DIRECTORY}:
+mingw, MSVC 14, glibc 2.0, OS X 10.9, FreeBSD 7.4, NetBSD 4.0.1, OpenBSD 4.9,
+AIX 7.1, HP-UX 11, Solaris 10.
+@item
On platforms where @code{off_t} is a 32-bit type, @code{open} may not work
correctly with files 2 GiB and larger. @xref{Large File Support}.
@item
Mac OS X 10.6, FreeBSD 8.4, NetBSD 5.1, OpenBSD 4.9, Minix 3.1.8, AIX 7.1, HP-UX 11.31, Solaris 10, Cygwin 1.7.1, mingw, MSVC 14.
@item
-@samp{O_DIRECTORY}, @samp{O_DSYNC}, @samp{O_NOCTTY},
+@samp{O_DSYNC}, @samp{O_NOCTTY},
@samp{O_NOFOLLOW}, @samp{O_RSYNC}, @samp{O_SYNC},
and @samp{O_TTY_INIT} are not defined on some platforms.
When not otherwise defined, Gnulib defines these macros to 0,
When not otherwise defined, Gnulib defines these macros to 0,
which is generally safe.
+@item
+@samp{O_DIRECTORY} is not defined on some platforms:
+mingw, MSVC 14, glibc 2.0, OS X 10.9, FreeBSD 7.4, NetBSD 4.0.1, OpenBSD 4.9,
+AIX 7.1, HP-UX 11, Solaris 10.
+
@item
@samp{FD_CLOEXEC}, @samp{F_DUPFD}, and @samp{F_GETFD} are not defined
on some platforms:
#endif
#ifndef O_DIRECTORY
-# define O_DIRECTORY 0
+# define O_DIRECTORY 0x20000000 /* Try to not collide with system O_* flags. */
#endif
#ifndef O_DSYNC
#include <sys/stat.h>
#include <unistd.h>
+#ifndef OPEN_TRAILING_SLASH_BUG
+# define OPEN_TRAILING_SLASH_BUG false
+#endif
+
#ifndef REPLACE_OPEN_DIRECTORY
-# define REPLACE_OPEN_DIRECTORY 0
+# define REPLACE_OPEN_DIRECTORY false
#endif
int
open (const char *filename, int flags, ...)
{
- /* 0 = unknown, 1 = yes, -1 = no. */
-#if GNULIB_defined_O_CLOEXEC
- int have_cloexec = -1;
-#else
- static int have_cloexec;
-#endif
-
- mode_t mode;
- int fd;
+ mode_t mode = 0;
- mode = 0;
if (flags & O_CREAT)
{
va_list arg;
filename = "NUL";
#endif
-#if OPEN_TRAILING_SLASH_BUG
/* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename
ends in a slash, as POSIX says such a filename must name a directory
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
directories,
- if O_WRONLY or O_RDWR is specified, open() must fail because the
file does not contain a '.' directory. */
- if ((flags & O_CREAT)
- || (flags & O_ACCMODE) == O_RDWR
- || (flags & O_ACCMODE) == O_WRONLY)
+ if (OPEN_TRAILING_SLASH_BUG
+ && (flags & O_CREAT
+ || (flags & O_ACCMODE) == O_RDWR
+ || (flags & O_ACCMODE) == O_WRONLY))
{
size_t len = strlen (filename);
if (len > 0 && filename[len - 1] == '/')
return -1;
}
}
+
+ /* 0 = unknown, 1 = yes, -1 = no. */
+#if GNULIB_defined_O_CLOEXEC
+ int have_cloexec = -1;
+#else
+ static int have_cloexec;
#endif
- fd = orig_open (filename,
- flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode);
+ int fd = orig_open (filename,
+ flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode);
if (flags & O_CLOEXEC)
{
}
#endif
-#if OPEN_TRAILING_SLASH_BUG
- /* If the filename ends in a slash and fd does not refer to a directory,
- then fail.
- Rationale: POSIX says such a filename must name a directory
+ /* If the filename ends in a slash or O_DIRECTORY is given,
+ then fail if fd does not refer to a directory.
+ Rationale: A filename ending in slash cannot name a non-directory
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
"A pathname that contains at least one non-<slash> character and that
ends with one or more trailing <slash> characters shall not be resolved
<slash> characters names an existing directory"
If the named file without the slash is not a directory, open() must fail
with ENOTDIR. */
- if (fd >= 0)
+ if (((!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)
+ || OPEN_TRAILING_SLASH_BUG)
+ && 0 <= fd)
{
- /* We know len is positive, since open did not fail with ENOENT. */
- size_t len = strlen (filename);
- if (filename[len - 1] == '/')
+ /* FILENAME must be nonempty, as open did not fail with ENOENT. */
+ if ((!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)
+ || filename[strlen (filename) - 1] == '/')
{
struct stat statbuf;
-
- if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+ int r = fstat (fd, &statbuf);
+ if (r < 0 || !S_ISDIR (statbuf.st_mode))
{
+ int err = r < 0 ? errno : ENOTDIR;
close (fd);
- errno = ENOTDIR;
+ errno = err;
return -1;
}
}
}
-#endif
#if REPLACE_FCHDIR
if (!REPLACE_OPEN_DIRECTORY && 0 <= fd)
#include <sys/stat.h>
#include <errno.h>
+#ifndef OPEN_TRAILING_SLASH_BUG
+# define OPEN_TRAILING_SLASH_BUG false
+#endif
+
#if HAVE_OPENAT
/* Like openat, but support O_CLOEXEC and work around Solaris 9 bugs
va_end (arg);
}
-# if OPEN_TRAILING_SLASH_BUG
/* Fail if one of O_CREAT, O_WRONLY, O_RDWR is specified and the filename
ends in a slash, as POSIX says such a filename must name a directory
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
directories,
- if O_WRONLY or O_RDWR is specified, open() must fail because the
file does not contain a '.' directory. */
- if ((flags & O_CREAT)
- || (flags & O_ACCMODE) == O_RDWR
- || (flags & O_ACCMODE) == O_WRONLY)
+ if (OPEN_TRAILING_SLASH_BUG
+ && (flags & O_CREAT
+ || (flags & O_ACCMODE) == O_RDWR
+ || (flags & O_ACCMODE) == O_WRONLY))
{
size_t len = strlen (filename);
if (len > 0 && filename[len - 1] == '/')
return -1;
}
}
-# endif
/* 0 = unknown, 1 = yes, -1 = no. */
-#if GNULIB_defined_O_CLOEXEC
+# if GNULIB_defined_O_CLOEXEC
int have_cloexec = -1;
-#else
+# else
static int have_cloexec;
-#endif
+# endif
int fd = orig_openat (dfd, filename,
flags & ~(have_cloexec < 0 ? O_CLOEXEC : 0), mode);
}
-# if OPEN_TRAILING_SLASH_BUG
- /* If the filename ends in a slash and fd does not refer to a directory,
- then fail.
- Rationale: POSIX says such a filename must name a directory
+ /* If the filename ends in a slash or O_DIRECTORY is given,
+ then fail if fd does not refer to a directory.
+ Rationale: A filename ending in slash cannot name a non-directory
<https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_13>:
"A pathname that contains at least one non-<slash> character and that
ends with one or more trailing <slash> characters shall not be resolved
<slash> characters names an existing directory"
If the named file without the slash is not a directory, open() must fail
with ENOTDIR. */
- if (fd >= 0)
+ if (((!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)
+ || OPEN_TRAILING_SLASH_BUG)
+ && 0 <= fd)
{
- /* We know len is positive, since open did not fail with ENOENT. */
- size_t len = strlen (filename);
- if (filename[len - 1] == '/')
+ /* FILENAME must be nonempty, as open did not fail with ENOENT. */
+ if ((!HAVE_WORKING_O_DIRECTORY && flags & O_DIRECTORY)
+ || filename[strlen (filename) - 1] == '/')
{
struct stat statbuf;
-
- if (fstat (fd, &statbuf) >= 0 && !S_ISDIR (statbuf.st_mode))
+ int r = fstat (fd, &statbuf);
+ if (r < 0 || !S_ISDIR (statbuf.st_mode))
{
+ int err = r < 0 ? errno : ENOTDIR;
close (fd);
- errno = ENOTDIR;
+ errno = err;
return -1;
}
}
}
-# endif
return fd;
}
# fcntl-o.m4
-# serial 8
+# serial 9
dnl Copyright (C) 2006, 2009-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
AC_PREREQ([2.60])
-# Test whether the flags O_NOATIME and O_NOFOLLOW actually work.
+# Test whether the flags O_DIRECTORY, O_NOATIME and O_NOFOLLOW actually work.
+# Define HAVE_WORKING_O_DIRECTORY to 1 if O_DIRECTORY works, or to 0 otherwise.
# Define HAVE_WORKING_O_NOATIME to 1 if O_NOATIME works, or to 0 otherwise.
# Define HAVE_WORKING_O_NOFOLLOW to 1 if O_NOFOLLOW works, or to 0 otherwise.
AC_DEFUN([gl_FCNTL_O_FLAGS],
# include <stdlib.h>
# defined sleep(n) _sleep ((n) * 1000)
#endif
+ #include <errno.h>
#include <fcntl.h>
]GL_MDA_DEFINES[
+ #ifndef O_DIRECTORY
+ #define O_DIRECTORY 0
+ #endif
#ifndef O_NOATIME
#define O_NOATIME 0
#endif
#ifndef O_NOFOLLOW
#define O_NOFOLLOW 0
#endif
+ #ifndef O_SEARCH
+ #define O_SEARCH O_RDONLY
+ #endif
static int const constants[] =
{
O_CREAT, O_EXCL, O_NOCTTY, O_TRUNC, O_APPEND,
]],
[[
int result = !constants;
+
+ {
+ int fd = open ("confdefs.h", O_SEARCH | O_DIRECTORY);
+ result |= ! (fd < 0 && errno == ENOTDIR);
+ if (0 <= fd)
+ close (fd);
+ }
+
#if HAVE_SYMLINK
{
static char const sym[] = "conftest.sym";
}
return result;]])],
[gl_cv_header_working_fcntl_h=yes],
- [case $? in #(
- 4) gl_cv_header_working_fcntl_h='no (bad O_NOFOLLOW)';; #(
- 64) gl_cv_header_working_fcntl_h='no (bad O_NOATIME)';; #(
- 68) gl_cv_header_working_fcntl_h='no (bad O_NOATIME, O_NOFOLLOW)';; #(
- *) gl_cv_header_working_fcntl_h='no';;
- esac],
- [case "$host_os" in
- # Guess 'no' on native Windows.
- mingw* | windows*) gl_cv_header_working_fcntl_h='no' ;;
- *) gl_cv_header_working_fcntl_h=cross-compiling ;;
- esac
- ])
- ])
+ [AS_CASE([$?],
+ [ 1], [gl_cv_header_working_fcntl_h="no (bad O_DIRECTORY)"],
+ [ 4], [gl_cv_header_working_fcntl_h="no (bad O_NOFOLLOW)"],
+ [ 5], [gl_cv_header_working_fcntl_h="no (bad O_DIRECTORY, O_NOFOLLOW)"],
+ [64], [gl_cv_header_working_fcntl_h="no (bad O_NOATIME)"],
+ [65], [gl_cv_header_working_fcntl_h="no (bad O_DIRECTORY, O_NOATIME)"],
+ [68], [gl_cv_header_working_fcntl_h="no (bad O_NOATIME, O_NOFOLLOW)"],
+ [69], [gl_cv_header_working_fcntl_h="no (bad O_DIRECTORY, O_NOATIME, O_NOFOLLOW)"],
+ [gl_cv_header_working_fcntl_h="no"])],
+ [AS_CASE([$host_os,$gl_cross_guess_normal],
+ # The O_DIRECTORY test is known to fail on Mac OS X 10.4.11 (2007)
+ # (see <https://bugs.gnu.org/78509#95>)
+ # and to succeed on macOS 12.6 [darwin21.6.0] (2021).
+ # For now, guess it fails on macOS 12.5 and earlier.
+ [darwin[[0-9]].*yes | darwin1[[0-9]]*.*yes | darwin20.*yes | \
+ darwin21.[[0-5]].*yes],
+ [gl_cv_header_working_fcntl_h="guessing no (bad O_DIRECTORY)"],
+ # Known to be "no" on native MS-Windows.
+ [mingw* | windows*],
+ [gl_cv_header_working_fcntl_h=no],
+ [gl_cv_header_working_fcntl_h=$gl_cross_guess_normal])])])
+
+ AS_CASE([$gl_cv_header_working_fcntl_h],
+ [*O_DIRECTORY* | *no], [ac_val=0], [ac_val=1])
+ AC_DEFINE_UNQUOTED([HAVE_WORKING_O_DIRECTORY], [$ac_val],
+ [Define to 1 if O_DIRECTORY works, 0 otherwise.])
- case $gl_cv_header_working_fcntl_h in #(
- *O_NOATIME* | no | cross-compiling) ac_val=0;; #(
- *) ac_val=1;;
- esac
+ AS_CASE([$gl_cv_header_working_fcntl_h],
+ [*O_NOATIME* | *no], [ac_val=0], [ac_val=1])
AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOATIME], [$ac_val],
- [Define to 1 if O_NOATIME works.])
+ [Define to 1 if O_NOATIME works, 0 otherwise.])
- case $gl_cv_header_working_fcntl_h in #(
- *O_NOFOLLOW* | no | cross-compiling) ac_val=0;; #(
- *) ac_val=1;;
- esac
+ AS_CASE([$gl_cv_header_working_fcntl_h],
+ [*O_NOFOLLOW* | *no], [ac_val=0], [ac_val=1])
AC_DEFINE_UNQUOTED([HAVE_WORKING_O_NOFOLLOW], [$ac_val],
- [Define to 1 if O_NOFOLLOW works.])
+ [Define to 1 if O_NOFOLLOW works, 0 otherwise.])
])
# open.m4
-# serial 16
+# serial 17
dnl Copyright (C) 2007-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
[
AC_REQUIRE([AC_CANONICAL_HOST])
AC_REQUIRE([gl_PREPROC_O_CLOEXEC])
+ AC_REQUIRE([gl_FCNTL_O_FLAGS])
+ AS_CASE([$gl_cv_header_working_fcntl_h],
+ [*O_DIRECTORY* | *no], [REPLACE_OPEN=1])
case "$host_os" in
mingw* | windows* | pw*)
REPLACE_OPEN=1
# openat.m4
-# serial 46
+# serial 47
dnl Copyright (C) 2004-2025 Free Software Foundation, Inc.
dnl This file is free software; the Free Software Foundation
dnl gives unlimited permission to copy and/or distribute it,
AC_REQUIRE([gl_FCNTL_H_DEFAULTS])
AC_REQUIRE([gl_USE_SYSTEM_EXTENSIONS])
AC_CHECK_FUNCS_ONCE([openat])
+ AC_REQUIRE([gl_FCNTL_O_FLAGS])
AC_REQUIRE([gl_FUNC_LSTAT_FOLLOWS_SLASHED_SYMLINK])
AC_REQUIRE([gl_PREPROC_O_CLOEXEC])
- case $ac_cv_func_openat+$gl_cv_func_lstat_dereferences_slashed_symlink+$gl_cv_macro_O_CLOEXEC in
- yes+*yes+yes)
- ;;
- yes+*)
- # Solaris 10 lacks O_CLOEXEC.
- # Solaris 9 has *at functions, but uniformly mishandles trailing
- # slash in all of them.
- REPLACE_OPENAT=1
- ;;
- *)
- HAVE_OPENAT=0
- ;;
- esac
+ AS_CASE([$ac_cv_func_openat+$gl_cv_header_working_fcntl_h+$gl_cv_func_lstat_dereferences_slashed_symlink+$gl_cv_macro_O_CLOEXEC],
+ [yes+*O_DIRECTORY*+*+* | yes+*no+*+*], [REPLACE_OPENAT=1],
+ [yes+*+*yes+yes], [],
+ [yes+*], [REPLACE_OPENAT=1],
+ [HAVE_OPENAT=0])
])
# Prerequisites of lib/openat.c.
ASSERT (func (BASE "file/", O_RDONLY) == -1);
ASSERT (errno == ENOTDIR || errno == EISDIR || errno == EINVAL);
+ /* Cannot open non-directory with O_DIRECTORY. */
+ errno = 0;
+ ASSERT (func (BASE "file", O_RDONLY | O_DIRECTORY) == -1);
+ ASSERT (errno == ENOTDIR);
+
/* Directories cannot be opened for writing. */
errno = 0;
ASSERT (func (".", O_WRONLY) == -1);