Issue #18571: Implementation of the PEP 446: file descriptors and file handles
are now created non-inheritable; add functions os.get/set_inheritable(),
os.get/set_handle_inheritable() and socket.socket.get/set_inheritable().
diff --git a/Python/fileutils.c b/Python/fileutils.c
index 6983855..1cc7480 100644
--- a/Python/fileutils.c
+++ b/Python/fileutils.c
@@ -9,10 +9,29 @@
 #include <langinfo.h>
 #endif
 
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif /* HAVE_FCNTL_H */
+
 #ifdef __APPLE__
 extern wchar_t* _Py_DecodeUTF8_surrogateescape(const char *s, Py_ssize_t size);
 #endif
 
+#ifdef O_CLOEXEC
+/* Does open() supports the O_CLOEXEC flag? Possible values:
+
+   -1: unknown
+    0: open() ignores O_CLOEXEC flag, ex: Linux kernel older than 2.6.23
+    1: open() supports O_CLOEXEC flag, close-on-exec is set
+
+   The flag is used by _Py_open(), io.FileIO and os.open() */
+int _Py_open_cloexec_works = -1;
+#endif
+
 PyObject *
 _Py_device_encoding(int fd)
 {
@@ -547,14 +566,215 @@
 
 #endif
 
-/* Open a file. Use _wfopen() on Windows, encode the path to the locale
-   encoding and use fopen() otherwise. */
+int
+get_inheritable(int fd, int raise)
+{
+#ifdef MS_WINDOWS
+    HANDLE handle;
+    DWORD flags;
 
+    if (!_PyVerify_fd(fd)) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    handle = (HANDLE)_get_osfhandle(fd);
+    if (handle == INVALID_HANDLE_VALUE) {
+        if (raise)
+            PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+
+    if (!GetHandleInformation(handle, &flags)) {
+        if (raise)
+            PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+
+    return (flags & HANDLE_FLAG_INHERIT);
+#else
+    int flags;
+
+    flags = fcntl(fd, F_GETFD, 0);
+    if (flags == -1) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+    return !(flags & FD_CLOEXEC);
+#endif
+}
+
+/* Get the inheritable flag of the specified file descriptor.
+   Return 1 if it the file descriptor can be inherited, 0 if it cannot,
+   raise an exception and return -1 on error. */
+int
+_Py_get_inheritable(int fd)
+{
+    return get_inheritable(fd, 1);
+}
+
+static int
+set_inheritable(int fd, int inheritable, int raise, int *atomic_flag_works)
+{
+#ifdef MS_WINDOWS
+    HANDLE handle;
+    DWORD flags;
+#elif defined(HAVE_SYS_IOCTL_H) && defined(FIOCLEX) && defined(FIONCLEX)
+    int request;
+    int err;
+#elif defined(HAVE_FCNTL_H)
+    int flags;
+    int res;
+#endif
+
+    /* atomic_flag_works can only be used to make the file descriptor
+       non-inheritable */
+    assert(!(atomic_flag_works != NULL && inheritable));
+
+    if (atomic_flag_works != NULL && !inheritable) {
+        if (*atomic_flag_works == -1) {
+            int inheritable = get_inheritable(fd, raise);
+            if (inheritable == -1)
+                return -1;
+            *atomic_flag_works = !inheritable;
+        }
+
+        if (*atomic_flag_works)
+            return 0;
+    }
+
+#ifdef MS_WINDOWS
+    if (!_PyVerify_fd(fd)) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    handle = (HANDLE)_get_osfhandle(fd);
+    if (handle == INVALID_HANDLE_VALUE) {
+        if (raise)
+            PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+
+    if (inheritable)
+        flags = HANDLE_FLAG_INHERIT;
+    else
+        flags = 0;
+    if (!SetHandleInformation(handle, HANDLE_FLAG_INHERIT, flags)) {
+        if (raise)
+            PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+    return 0;
+
+#elif defined(HAVE_SYS_IOCTL_H) && defined(FIOCLEX) && defined(FIONCLEX)
+    if (inheritable)
+        request = FIONCLEX;
+    else
+        request = FIOCLEX;
+    err = ioctl(fd, request);
+    if (err) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+    return 0;
+
+#else
+    flags = fcntl(fd, F_GETFD);
+    if (flags < 0) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    if (inheritable)
+        flags &= ~FD_CLOEXEC;
+    else
+        flags |= FD_CLOEXEC;
+    res = fcntl(fd, F_SETFD, flags);
+    if (res < 0) {
+        if (raise)
+            PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+    return 0;
+#endif
+}
+
+/* Make the file descriptor non-inheritable.
+   Return 0 success, set errno and return -1 on error. */
+static int
+make_non_inheritable(int fd)
+{
+    return set_inheritable(fd, 0, 0, NULL);
+}
+
+/* Set the inheritable flag of the specified file descriptor.
+   On success: return 0, on error: raise an exception if raise is nonzero
+   and return -1.
+
+   If atomic_flag_works is not NULL:
+
+    * if *atomic_flag_works==-1, check if the inheritable is set on the file
+      descriptor: if yes, set *atomic_flag_works to 1, otherwise set to 0 and
+      set the inheritable flag
+    * if *atomic_flag_works==1: do nothing
+    * if *atomic_flag_works==0: set inheritable flag to False
+
+   Set atomic_flag_works to NULL if no atomic flag was used to create the
+   file descriptor.
+
+   atomic_flag_works can only be used to make a file descriptor
+   non-inheritable: atomic_flag_works must be NULL if inheritable=1. */
+int
+_Py_set_inheritable(int fd, int inheritable, int *atomic_flag_works)
+{
+    return set_inheritable(fd, inheritable, 1, atomic_flag_works);
+}
+
+/* Open a file with the specified flags (wrapper to open() function).
+   The file descriptor is created non-inheritable. */
+int
+_Py_open(const char *pathname, int flags)
+{
+    int fd;
+#ifdef MS_WINDOWS
+    fd = open(pathname, flags | O_NOINHERIT);
+    if (fd < 0)
+        return fd;
+#else
+
+    int *atomic_flag_works;
+#ifdef O_CLOEXEC
+    atomic_flag_works = &_Py_open_cloexec_works;
+    flags |= O_CLOEXEC;
+#else
+    atomic_flag_works = NULL;
+#endif
+    fd = open(pathname, flags);
+    if (fd < 0)
+        return fd;
+
+    if (set_inheritable(fd, 0, 0, atomic_flag_works) < 0) {
+        close(fd);
+        return -1;
+    }
+#endif   /* !MS_WINDOWS */
+    return fd;
+}
+
+/* Open a file. Use _wfopen() on Windows, encode the path to the locale
+   encoding and use fopen() otherwise. The file descriptor is created
+   non-inheritable. */
 FILE *
 _Py_wfopen(const wchar_t *path, const wchar_t *mode)
 {
-#ifndef MS_WINDOWS
     FILE *f;
+#ifndef MS_WINDOWS
     char *cpath;
     char cmode[10];
     size_t r;
@@ -568,21 +788,42 @@
         return NULL;
     f = fopen(cpath, cmode);
     PyMem_Free(cpath);
-    return f;
 #else
-    return _wfopen(path, mode);
+    f = _wfopen(path, mode);
 #endif
+    if (f == NULL)
+        return NULL;
+    if (make_non_inheritable(fileno(f)) < 0) {
+        fclose(f);
+        return NULL;
+    }
+    return f;
 }
 
-/* Call _wfopen() on Windows, or encode the path to the filesystem encoding and
-   call fopen() otherwise.
+/* Wrapper to fopen(). The file descriptor is created non-inheritable. */
+FILE*
+_Py_fopen(const char *pathname, const char *mode)
+{
+    FILE *f = fopen(pathname, mode);
+    if (f == NULL)
+        return NULL;
+    if (make_non_inheritable(fileno(f)) < 0) {
+        fclose(f);
+        return NULL;
+    }
+    return f;
+}
+
+/* Open a file. Call _wfopen() on Windows, or encode the path to the filesystem
+   encoding and call fopen() otherwise. The file descriptor is created
+   non-inheritable.
 
    Return the new file object on success, or NULL if the file cannot be open or
-   (if PyErr_Occurred()) on unicode error */
-
+   (if PyErr_Occurred()) on unicode error. */
 FILE*
-_Py_fopen(PyObject *path, const char *mode)
+_Py_fopen_obj(PyObject *path, const char *mode)
 {
+    FILE *f;
 #ifdef MS_WINDOWS
     wchar_t *wpath;
     wchar_t wmode[10];
@@ -602,16 +843,21 @@
     if (usize == 0)
         return NULL;
 
-    return _wfopen(wpath, wmode);
+    f = _wfopen(wpath, wmode);
 #else
-    FILE *f;
     PyObject *bytes;
     if (!PyUnicode_FSConverter(path, &bytes))
         return NULL;
     f = fopen(PyBytes_AS_STRING(bytes), mode);
     Py_DECREF(bytes);
-    return f;
 #endif
+    if (f == NULL)
+        return NULL;
+    if (make_non_inheritable(fileno(f)) < 0) {
+        fclose(f);
+        return NULL;
+    }
+    return f;
 }
 
 #ifdef HAVE_READLINK
@@ -729,3 +975,72 @@
 #endif
 }
 
+/* Duplicate a file descriptor. The new file descriptor is created as
+   non-inheritable. Return a new file descriptor on success, raise an OSError
+   exception and return -1 on error.
+
+   The GIL is released to call dup(). The caller must hold the GIL. */
+int
+_Py_dup(int fd)
+{
+#ifdef MS_WINDOWS
+    HANDLE handle;
+    DWORD ftype;
+#endif
+
+    if (!_PyVerify_fd(fd)) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+#ifdef MS_WINDOWS
+    handle = (HANDLE)_get_osfhandle(fd);
+    if (handle == INVALID_HANDLE_VALUE) {
+        PyErr_SetFromWindowsErr(0);
+        return -1;
+    }
+
+    /* get the file type, ignore the error if it failed */
+    ftype = GetFileType(handle);
+
+    Py_BEGIN_ALLOW_THREADS
+    fd = dup(fd);
+    Py_END_ALLOW_THREADS
+    if (fd < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    /* Character files like console cannot be make non-inheritable */
+    if (ftype != FILE_TYPE_CHAR) {
+        if (_Py_set_inheritable(fd, 0, NULL) < 0) {
+            close(fd);
+            return -1;
+        }
+    }
+#elif defined(HAVE_FCNTL_H) && defined(F_DUPFD_CLOEXEC)
+    Py_BEGIN_ALLOW_THREADS
+    fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+    Py_END_ALLOW_THREADS
+    if (fd < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+#else
+    Py_BEGIN_ALLOW_THREADS
+    fd = dup(fd);
+    Py_END_ALLOW_THREADS
+    if (fd < 0) {
+        PyErr_SetFromErrno(PyExc_OSError);
+        return -1;
+    }
+
+    if (_Py_set_inheritable(fd, 0, NULL) < 0) {
+        close(fd);
+        return -1;
+    }
+#endif
+    return fd;
+}
+