Tim Rühsen wrote on 2020-05-12:
> > How about using open() with O_CLOEXEC, and then fdopen()?
> 
> Thanks. Sure, this is possible. Doing so at dozens of places (or more)
> asks for an implementation in gnulib :)

Additionally, gnulib is the right place to do this because - as Eric said -
the 'e' flag is already scheduled for being added to the next POSIX version:
https://www.austingroupbugs.net/view.php?id=411

My evaluation of current platform support was incorrect: Current versions of
FreeBSD, NetBSD, OpenBSD, Solaris, Cygwin, and even Minix already support it.


2020-05-24  Bruno Haible  <br...@clisp.org>

        fopen-gnu: Add tests.
        * tests/test-fopen-gnu.c: New file.
        * modules/fopen-gnu-tests: New file.

        fopen-gnu: New module.
        Suggested by Tim Rühsen <tim.rueh...@gmx.de> in
        <https://lists.gnu.org/archive/html/bug-gnulib/2020-05/msg00119.html>.
        * lib/fopen.c (rpl_fopen): When the fopen-gnu module is enabled and the
        mode contains an 'x' or 'e' flag, use open() followed by fdopen().
        * m4/fopen.m4 (gl_FUNC_FOPEN_GNU): New macro.
        * modules/fopen-gnu: New file.
        * doc/posix-functions/fopen.texi: Document the 'fopen-gnu' module.

From 10cb4be2a2114dd6fff347acc9841d7904636adf Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 24 May 2020 20:38:53 +0200
Subject: [PATCH 1/2] fopen-gnu: New module.
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Suggested by Tim Rühsen <tim.rueh...@gmx.de> in
<https://lists.gnu.org/archive/html/bug-gnulib/2020-05/msg00119.html>.

* lib/fopen.c (rpl_fopen): When the fopen-gnu module is enabled and the
mode contains an 'x' or 'e' flag, use open() followed by fdopen().
* m4/fopen.m4 (gl_FUNC_FOPEN_GNU): New macro.
* modules/fopen-gnu: New file.
* doc/posix-functions/fopen.texi: Document the 'fopen-gnu' module.
---
 ChangeLog                      | 11 +++++
 doc/posix-functions/fopen.texi | 19 ++++++++-
 lib/fopen.c                    | 92 ++++++++++++++++++++++++++++++++++++++++--
 m4/fopen.m4                    | 87 ++++++++++++++++++++++++++++++++++++++-
 modules/fopen-gnu              | 27 +++++++++++++
 5 files changed, 229 insertions(+), 7 deletions(-)
 create mode 100644 modules/fopen-gnu

diff --git a/ChangeLog b/ChangeLog
index ae9fae4..fab0d87 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,16 @@
 2020-05-24  Bruno Haible  <br...@clisp.org>
 
+	fopen-gnu: New module.
+	Suggested by Tim Rühsen <tim.rueh...@gmx.de> in
+	<https://lists.gnu.org/archive/html/bug-gnulib/2020-05/msg00119.html>.
+	* lib/fopen.c (rpl_fopen): When the fopen-gnu module is enabled and the
+	mode contains an 'x' or 'e' flag, use open() followed by fdopen().
+	* m4/fopen.m4 (gl_FUNC_FOPEN_GNU): New macro.
+	* modules/fopen-gnu: New file.
+	* doc/posix-functions/fopen.texi: Document the 'fopen-gnu' module.
+
+2020-05-24  Bruno Haible  <br...@clisp.org>
+
 	open, openat: Really support O_CLOEXEC.
 	* lib/open.c (open): When have_cloexec is still undecided, do pass a
 	O_CLOEXEC flag to orig_open.
diff --git a/doc/posix-functions/fopen.texi b/doc/posix-functions/fopen.texi
index 308d676..6c562a6 100644
--- a/doc/posix-functions/fopen.texi
+++ b/doc/posix-functions/fopen.texi
@@ -4,9 +4,9 @@
 
 POSIX specification:@* @url{https://pubs.opengroup.org/onlinepubs/9699919799/functions/fopen.html}
 
-Gnulib module: fopen
+Gnulib module: fopen or fopen-gnu
 
-Portability problems fixed by Gnulib:
+Portability problems fixed by either Gnulib module @code{fopen} or @code{fopen-gnu}:
 @itemize
 @item
 This function does not fail when the file name argument ends in a slash
@@ -21,6 +21,21 @@ On Windows platforms (excluding Cygwin), this function does usually not
 recognize the @file{/dev/null} filename.
 @end itemize
 
+Portability problems fixed by Gnulib module @code{fopen-gnu}:
+@itemize
+@item
+This function does not support the mode character
+@samp{x} (corresponding to @code{O_EXCL}), introduced in ISO C11,
+on some platforms:
+FreeBSD 8.2, NetBSD 6.1, OpenBSD 5.6, Minix 3.2, AIX 6.1, HP-UX 11.31, IRIX 6.5, Solaris 11.3, Cygwin 1.7.16 (2012), mingw, MSVC 14.
+@item
+This function does not support the mode character
+@samp{e} (corresponding to @code{O_CLOEXEC}),
+introduced into a future POSIX revision through
+@url{https://www.austingroupbugs.net/view.php?id=411}, on some platforms:
+glibc 2.6, Mac OS X 10.13, FreeBSD 9.0, NetBSD 5.1, OpenBSD 5.6, Minix 3.2, AIX 7.2, HP-UX 11.31, IRIX 6.5, Solaris 11.3, Cygwin 1.7.16 (2012), mingw, MSVC 14.
+@end itemize
+
 Portability problems not fixed by Gnulib:
 @itemize
 @item
diff --git a/lib/fopen.c b/lib/fopen.c
index ad6511d..20065e4 100644
--- a/lib/fopen.c
+++ b/lib/fopen.c
@@ -49,6 +49,12 @@ rpl_fopen (const char *filename, const char *mode)
 {
   int open_direction;
   int open_flags_standard;
+#if GNULIB_FOPEN_GNU
+  int open_flags_gnu;
+# define BUF_SIZE 80
+  char fdopen_mode_buf[BUF_SIZE + 1];
+#endif
+  int open_flags;
 
 #if defined _WIN32 && ! defined __CYGWIN__
   if (strcmp (filename, "/dev/null") == 0)
@@ -58,35 +64,88 @@ rpl_fopen (const char *filename, const char *mode)
   /* Parse the mode.  */
   open_direction = 0;
   open_flags_standard = 0;
+#if GNULIB_FOPEN_GNU
+  open_flags_gnu = 0;
+#endif
   {
-    const char *m;
+    const char *p = mode;
+#if GNULIB_FOPEN_GNU
+    char *q = fdopen_mode_buf;
+#endif
 
-    for (m = mode; *m != '\0'; m++)
+    for (; *p != '\0'; p++)
       {
-        switch (*m)
+        switch (*p)
           {
           case 'r':
             open_direction = O_RDONLY;
+#if GNULIB_FOPEN_GNU
+            if (q < fdopen_mode_buf + BUF_SIZE)
+              *q++ = *p;
+#endif
             continue;
           case 'w':
             open_direction = O_WRONLY;
             open_flags_standard |= O_CREAT | O_TRUNC;
+#if GNULIB_FOPEN_GNU
+            if (q < fdopen_mode_buf + BUF_SIZE)
+              *q++ = *p;
+#endif
             continue;
           case 'a':
             open_direction = O_WRONLY;
             open_flags_standard |= O_CREAT | O_APPEND;
+#if GNULIB_FOPEN_GNU
+            if (q < fdopen_mode_buf + BUF_SIZE)
+              *q++ = *p;
+#endif
             continue;
           case 'b':
+#if GNULIB_FOPEN_GNU
+            if (q < fdopen_mode_buf + BUF_SIZE)
+              *q++ = *p;
+#endif
             continue;
           case '+':
             open_direction = O_RDWR;
+#if GNULIB_FOPEN_GNU
+            if (q < fdopen_mode_buf + BUF_SIZE)
+              *q++ = *p;
+#endif
             continue;
+#if GNULIB_FOPEN_GNU
+          case 'x':
+            open_flags_gnu |= O_EXCL;
+            continue;
+          case 'e':
+            open_flags_gnu |= O_CLOEXEC;
+            continue;
+#endif
           default:
             break;
           }
+#if GNULIB_FOPEN_GNU
+        /* The rest of the mode string can be a platform-dependent extension.
+           Copy it unmodified.  */
+        {
+          size_t len = strlen (p);
+          if (len > fdopen_mode_buf + BUF_SIZE - q)
+            len = fdopen_mode_buf + BUF_SIZE - q;
+          memcpy (q, p, len);
+          q += len;
+        }
+#endif
         break;
       }
+#if GNULIB_FOPEN_GNU
+    *q = '\0';
+#endif
   }
+#if GNULIB_FOPEN_GNU
+  open_flags = open_flags_standard | open_flags_gnu;
+#else
+  open_flags = open_flags_standard;
+#endif
 
 #if FOPEN_TRAILING_SLASH_BUG
   /* Fail if the mode requires write access and the filename ends in a slash,
@@ -116,7 +175,7 @@ rpl_fopen (const char *filename, const char *mode)
             return NULL;
           }
 
-        fd = open (filename, open_direction | open_flags_standard);
+        fd = open (filename, open_direction | open_flags);
         if (fd < 0)
           return NULL;
 
@@ -127,7 +186,11 @@ rpl_fopen (const char *filename, const char *mode)
             return NULL;
           }
 
+# if GNULIB_FOPEN_GNU
+        fp = fdopen (fd, fdopen_mode_buf);
+# else
         fp = fdopen (fd, mode);
+# endif
         if (fp == NULL)
           {
             int saved_errno = errno;
@@ -139,5 +202,26 @@ rpl_fopen (const char *filename, const char *mode)
   }
 #endif
 
+#if GNULIB_FOPEN_GNU
+  if (open_flags_gnu != 0)
+    {
+      int fd;
+      FILE *fp;
+
+      fd = open (filename, open_direction | open_flags);
+      if (fd < 0)
+        return NULL;
+
+      fp = fdopen (fd, fdopen_mode_buf);
+      if (fp == NULL)
+        {
+          int saved_errno = errno;
+          close (fd);
+          errno = saved_errno;
+        }
+      return fp;
+    }
+#endif
+
   return orig_fopen (filename, mode);
 }
diff --git a/m4/fopen.m4 b/m4/fopen.m4
index 2a4b00d..8eab4a6 100644
--- a/m4/fopen.m4
+++ b/m4/fopen.m4
@@ -1,4 +1,4 @@
-# fopen.m4 serial 10
+# fopen.m4 serial 11
 dnl Copyright (C) 2007-2020 Free Software Foundation, Inc.
 dnl This file is free software; the Free Software Foundation
 dnl gives unlimited permission to copy and/or distribute it,
@@ -58,5 +58,90 @@ changequote([,])dnl
   esac
 ])
 
+AC_DEFUN([gl_FUNC_FOPEN_GNU],
+[
+  AC_REQUIRE([gl_FUNC_FOPEN])
+  AC_CACHE_CHECK([whether fopen supports the mode character 'x'],
+    [gl_cv_func_fopen_mode_x],
+    [rm -f conftest.x
+     AC_RUN_IFELSE(
+       [AC_LANG_SOURCE([[
+#include <stdio.h>
+#include <errno.h>
+int main ()
+{
+  FILE *fp;
+  fp = fopen ("conftest.x", "w");
+  fclose (fp);
+  fp = fopen ("conftest.x", "wx");
+  if (fp != NULL)
+    /* 'x' ignored */
+    return 1;
+  else if (errno == EEXIST)
+    return 0;
+  else
+    /* 'x' rejected */
+    return 2;
+}]])],
+       [gl_cv_func_fopen_mode_x=yes],
+       [gl_cv_func_fopen_mode_x=no],
+       [case "$host_os" in
+          # Guess yes on glibc and musl systems.
+          linux*-gnu* | gnu* | kfreebsd*-gnu | *-musl*)
+            gl_cv_func_fopen_mode_x="guessing yes" ;;
+          # If we don't know, obey --enable-cross-guesses.
+          *)
+            gl_cv_func_fopen_mode_x="$gl_cross_guess_normal" ;;
+        esac
+       ])
+     rm -f conftest.x
+    ])
+  AC_CACHE_CHECK([whether fopen supports the mode character 'e'],
+    [gl_cv_func_fopen_mode_e],
+    [echo foo > conftest.x
+     AC_RUN_IFELSE(
+       [AC_LANG_SOURCE([[
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+int main ()
+{
+  FILE *fp = fopen ("conftest.x", "re");
+  if (fp != NULL)
+    {
+      if (fcntl (fileno (fp), F_GETFD) & FD_CLOEXEC)
+        return 0;
+      else
+        /* 'e' ignored */
+        return 1;
+    }
+  else
+    /* 'e' rejected */
+    return 2;
+}]])],
+       [gl_cv_func_fopen_mode_e=yes],
+       [gl_cv_func_fopen_mode_e=no],
+       [case "$host_os" in
+          # Guess yes on glibc and musl systems.
+          linux*-gnu* | gnu* | kfreebsd*-gnu | *-musl*)
+            gl_cv_func_fopen_mode_e="guessing yes" ;;
+          # Guess no on native Windows.
+          mingw*)
+            gl_cv_func_fopen_mode_e="guessing no" ;;
+          # If we don't know, obey --enable-cross-guesses.
+          *)
+            gl_cv_func_fopen_mode_e="$gl_cross_guess_normal" ;;
+        esac
+       ])
+     rm -f conftest.x
+    ])
+  case "$gl_cv_func_fopen_mode_x" in
+    *no) REPLACE_FOPEN=1 ;;
+  esac
+  case "$gl_cv_func_fopen_mode_e" in
+    *no) REPLACE_FOPEN=1 ;;
+  esac
+])
+
 # Prerequisites of lib/fopen.c.
 AC_DEFUN([gl_PREREQ_FOPEN], [:])
diff --git a/modules/fopen-gnu b/modules/fopen-gnu
new file mode 100644
index 0000000..f0f2054
--- /dev/null
+++ b/modules/fopen-gnu
@@ -0,0 +1,27 @@
+Description:
+fopen() function: open a stream to a file, with GNU extensions.
+
+Files:
+
+Depends-on:
+fopen
+open            [test $REPLACE_FOPEN = 1]
+
+configure.ac:
+gl_FUNC_FOPEN_GNU
+if test $REPLACE_FOPEN = 1; then
+  AC_LIBOBJ([fopen])
+  gl_PREREQ_FOPEN
+fi
+gl_MODULE_INDICATOR([fopen-gnu])
+
+Makefile.am:
+
+Include:
+<stdio.h>
+
+License:
+LGPLv2+
+
+Maintainer:
+all
-- 
2.7.4

From 48888b25625ddcdcc296e4cc1b0cd13e417e54f0 Mon Sep 17 00:00:00 2001
From: Bruno Haible <br...@clisp.org>
Date: Sun, 24 May 2020 20:40:01 +0200
Subject: [PATCH 2/2] fopen-gnu: Add tests.

* tests/test-fopen-gnu.c: New file.
* modules/fopen-gnu-tests: New file.
---
 ChangeLog               |  4 +++
 modules/fopen-gnu-tests | 12 +++++++++
 tests/test-fopen-gnu.c  | 71 +++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 87 insertions(+)
 create mode 100644 modules/fopen-gnu-tests
 create mode 100644 tests/test-fopen-gnu.c

diff --git a/ChangeLog b/ChangeLog
index fab0d87..d7c1e8d 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,5 +1,9 @@
 2020-05-24  Bruno Haible  <br...@clisp.org>
 
+	fopen-gnu: Add tests.
+	* tests/test-fopen-gnu.c: New file.
+	* modules/fopen-gnu-tests: New file.
+
 	fopen-gnu: New module.
 	Suggested by Tim Rühsen <tim.rueh...@gmx.de> in
 	<https://lists.gnu.org/archive/html/bug-gnulib/2020-05/msg00119.html>.
diff --git a/modules/fopen-gnu-tests b/modules/fopen-gnu-tests
new file mode 100644
index 0000000..9e4c4ca
--- /dev/null
+++ b/modules/fopen-gnu-tests
@@ -0,0 +1,12 @@
+Files:
+tests/test-fopen-gnu.c
+tests/macros.h
+
+Depends-on:
+fcntl
+
+configure.ac:
+
+Makefile.am:
+TESTS += test-fopen-gnu
+check_PROGRAMS += test-fopen-gnu
diff --git a/tests/test-fopen-gnu.c b/tests/test-fopen-gnu.c
new file mode 100644
index 0000000..cae4042
--- /dev/null
+++ b/tests/test-fopen-gnu.c
@@ -0,0 +1,71 @@
+/* Test of opening a file stream.
+   Copyright (C) 2020 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 <https://www.gnu.org/licenses/>.  */
+
+/* Written by Bruno Haible <br...@clisp.org>, 2020.  */
+
+#include <config.h>
+
+/* Specification.  */
+#include <stdio.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "macros.h"
+
+#define BASE "test-fopen-gnu.t"
+
+int
+main (void)
+{
+  FILE *f;
+  int fd;
+  int flags;
+
+  /* Remove anything from prior partial run.  */
+  unlink (BASE "file");
+
+  /* Create the file.  */
+  f = fopen (BASE "file", "w");
+  ASSERT (f);
+  fd = fileno (f);
+  ASSERT (fd >= 0);
+  flags = fcntl (fd, F_GETFD);
+  ASSERT (flags >= 0);
+  ASSERT ((flags & FD_CLOEXEC) == 0);
+  ASSERT (fclose (f) == 0);
+
+  /* Create the file and check the 'e' mode.  */
+  f = fopen (BASE "file", "we");
+  ASSERT (f);
+  fd = fileno (f);
+  ASSERT (fd >= 0);
+  flags = fcntl (fd, F_GETFD);
+  ASSERT (flags >= 0);
+  ASSERT ((flags & FD_CLOEXEC) != 0);
+  ASSERT (fclose (f) == 0);
+
+  /* Open the file and check the 'x' mode.  */
+  f = fopen (BASE "file", "ax");
+  ASSERT (f == NULL);
+  ASSERT (errno == EEXIST);
+
+  /* Cleanup.  */
+  ASSERT (unlink (BASE "file") == 0);
+
+  return 0;
+}
-- 
2.7.4

Reply via email to