summaryrefslogtreecommitdiffstats
path: root/Python/fileutils.c
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2013-08-27 22:53:59 (GMT)
committerVictor Stinner <victor.stinner@gmail.com>2013-08-27 22:53:59 (GMT)
commitdaf455554bc21b6b5df0a016ab5fa639d36cc595 (patch)
tree216f52f9f6d9aed0406b2ce2574e5a02aa93e327 /Python/fileutils.c
parent46e1ce214b5711e8dae63a1b5a0a7aafb371baf0 (diff)
downloadcpython-daf455554bc21b6b5df0a016ab5fa639d36cc595.zip
cpython-daf455554bc21b6b5df0a016ab5fa639d36cc595.tar.gz
cpython-daf455554bc21b6b5df0a016ab5fa639d36cc595.tar.bz2
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().
Diffstat (limited to 'Python/fileutils.c')
-rw-r--r--Python/fileutils.c341
1 files changed, 328 insertions, 13 deletions
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 @@ _Py_stat(PyObject *path, struct stat *statbuf)
#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 @@ _Py_wfopen(const wchar_t *path, const wchar_t *mode)
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;
+}
- Return the new file object on success, or NULL if the file cannot be open or
- (if PyErr_Occurred()) on unicode error */
+/* 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. */
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 @@ _Py_fopen(PyObject *path, const char *mode)
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 @@ _Py_wgetcwd(wchar_t *buf, size_t size)
#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;
+}
+