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().
This commit is contained in:
Victor Stinner 2013-08-28 00:53:59 +02:00
parent 46e1ce214b
commit daf455554b
51 changed files with 1448 additions and 317 deletions

View file

@ -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;
}
/* 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 @@ _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;
}