Skip to content

Commit fbef473

Browse files
committed
Add _Py_IsValidFD() helper function
1 parent 4cba722 commit fbef473

File tree

4 files changed

+61
-54
lines changed

4 files changed

+61
-54
lines changed

Include/internal/pycore_fileutils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,9 @@ extern int _PyFile_Flush(PyObject *);
326326
extern int _Py_GetTicksPerSecond(long *ticks_per_second);
327327
#endif
328328

329+
// Export for '_testcapi' shared extension
330+
PyAPI_FUNC(int) _Py_IsValidFD(int fd);
331+
329332
#ifdef __cplusplus
330333
}
331334
#endif

Modules/_testcapi/run.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
#define PYTESTCAPI_NEED_INTERNAL_API
12
#include "parts.h"
23
#include "util.h"
4+
#include "pycore_fileutils.h" // _Py_IsValidFD()
35

46
#include <stdio.h>
57
#include <errno.h>
@@ -75,15 +77,14 @@ run_fileexflags(PyObject *mod, PyObject *pos_args)
7577

7678
result = PyRun_FileExFlags(fp, filename, start, globals, locals, closeit, pflags);
7779

78-
struct stat st;
79-
if (closeit && result && fstat(fd, &st) == 0) {
80+
if (closeit && result && _Py_IsValidFD(fd)) {
8081
PyErr_SetString(PyExc_AssertionError, "File was not closed after excution");
8182
Py_DECREF(result);
8283
fclose(fp);
8384
return NULL;
8485
}
8586

86-
if (!closeit && fstat(fd, &st) != 0) {
87+
if (!closeit && !_Py_IsValidFD(fd)) {
8788
PyErr_SetString(PyExc_AssertionError, "Bad file descriptor after excution");
8889
Py_XDECREF(result);
8990
return NULL;

Python/fileutils.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3050,3 +3050,53 @@ _Py_GetTicksPerSecond(long *ticks_per_second)
30503050
return 0;
30513051
}
30523052
#endif
3053+
3054+
3055+
/* Check if a file descriptor is valid or not.
3056+
Return 0 if the file descriptor is invalid, return non-zero otherwise. */
3057+
int
3058+
_Py_IsValidFD(int fd)
3059+
{
3060+
/* dup() is faster than fstat(): fstat() can require input/output operations,
3061+
whereas dup() doesn't. There is a low risk of EMFILE/ENFILE at Python
3062+
startup. Problem: dup() doesn't check if the file descriptor is valid on
3063+
some platforms.
3064+
3065+
fcntl(fd, F_GETFD) is even faster, because it only checks the process table.
3066+
It is preferred over dup() when available, since it cannot fail with the
3067+
"too many open files" error (EMFILE).
3068+
3069+
bpo-30225: On macOS Tiger, when stdout is redirected to a pipe and the other
3070+
side of the pipe is closed, dup(1) succeed, whereas fstat(1, &st) fails with
3071+
EBADF. FreeBSD has similar issue (bpo-32849).
3072+
3073+
Only use dup() on Linux where dup() is enough to detect invalid FD
3074+
(bpo-32849).
3075+
*/
3076+
if (fd < 0) {
3077+
return 0;
3078+
}
3079+
#if defined(F_GETFD) && ( \
3080+
defined(__linux__) || \
3081+
defined(__APPLE__) || \
3082+
defined(__wasm__))
3083+
return fcntl(fd, F_GETFD) >= 0;
3084+
#elif defined(__linux__)
3085+
int fd2 = dup(fd);
3086+
if (fd2 >= 0) {
3087+
close(fd2);
3088+
}
3089+
return (fd2 >= 0);
3090+
#elif defined(MS_WINDOWS)
3091+
HANDLE hfile;
3092+
_Py_BEGIN_SUPPRESS_IPH
3093+
hfile = (HANDLE)_get_osfhandle(fd);
3094+
_Py_END_SUPPRESS_IPH
3095+
return (hfile != INVALID_HANDLE_VALUE
3096+
&& GetFileType(hfile) != FILE_TYPE_UNKNOWN);
3097+
#else
3098+
struct stat st;
3099+
return (fstat(fd, &st) == 0);
3100+
#endif
3101+
}
3102+

Python/pylifecycle.c

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2400,54 +2400,6 @@ init_import_site(void)
24002400
return _PyStatus_OK();
24012401
}
24022402

2403-
/* Check if a file descriptor is valid or not.
2404-
Return 0 if the file descriptor is invalid, return non-zero otherwise. */
2405-
static int
2406-
is_valid_fd(int fd)
2407-
{
2408-
/* dup() is faster than fstat(): fstat() can require input/output operations,
2409-
whereas dup() doesn't. There is a low risk of EMFILE/ENFILE at Python
2410-
startup. Problem: dup() doesn't check if the file descriptor is valid on
2411-
some platforms.
2412-
2413-
fcntl(fd, F_GETFD) is even faster, because it only checks the process table.
2414-
It is preferred over dup() when available, since it cannot fail with the
2415-
"too many open files" error (EMFILE).
2416-
2417-
bpo-30225: On macOS Tiger, when stdout is redirected to a pipe and the other
2418-
side of the pipe is closed, dup(1) succeed, whereas fstat(1, &st) fails with
2419-
EBADF. FreeBSD has similar issue (bpo-32849).
2420-
2421-
Only use dup() on Linux where dup() is enough to detect invalid FD
2422-
(bpo-32849).
2423-
*/
2424-
if (fd < 0) {
2425-
return 0;
2426-
}
2427-
#if defined(F_GETFD) && ( \
2428-
defined(__linux__) || \
2429-
defined(__APPLE__) || \
2430-
defined(__wasm__))
2431-
return fcntl(fd, F_GETFD) >= 0;
2432-
#elif defined(__linux__)
2433-
int fd2 = dup(fd);
2434-
if (fd2 >= 0) {
2435-
close(fd2);
2436-
}
2437-
return (fd2 >= 0);
2438-
#elif defined(MS_WINDOWS)
2439-
HANDLE hfile;
2440-
_Py_BEGIN_SUPPRESS_IPH
2441-
hfile = (HANDLE)_get_osfhandle(fd);
2442-
_Py_END_SUPPRESS_IPH
2443-
return (hfile != INVALID_HANDLE_VALUE
2444-
&& GetFileType(hfile) != FILE_TYPE_UNKNOWN);
2445-
#else
2446-
struct stat st;
2447-
return (fstat(fd, &st) == 0);
2448-
#endif
2449-
}
2450-
24512403
/* returns Py_None if the fd is not valid */
24522404
static PyObject*
24532405
create_stdio(const PyConfig *config, PyObject* io,
@@ -2461,8 +2413,9 @@ create_stdio(const PyConfig *config, PyObject* io,
24612413
int buffering, isatty;
24622414
const int buffered_stdio = config->buffered_stdio;
24632415

2464-
if (!is_valid_fd(fd))
2416+
if (!_Py_IsValidFD(fd)) {
24652417
Py_RETURN_NONE;
2418+
}
24662419

24672420
/* stdin is always opened in buffered mode, first because it shouldn't
24682421
make a difference in common use cases, second because TextIOWrapper
@@ -2578,9 +2531,9 @@ create_stdio(const PyConfig *config, PyObject* io,
25782531
Py_XDECREF(text);
25792532
Py_XDECREF(raw);
25802533

2581-
if (PyErr_ExceptionMatches(PyExc_OSError) && !is_valid_fd(fd)) {
2534+
if (PyErr_ExceptionMatches(PyExc_OSError) && !_Py_IsValidFD(fd)) {
25822535
/* Issue #24891: the file descriptor was closed after the first
2583-
is_valid_fd() check was called. Ignore the OSError and set the
2536+
_Py_IsValidFD() check was called. Ignore the OSError and set the
25842537
stream to None. */
25852538
PyErr_Clear();
25862539
Py_RETURN_NONE;

0 commit comments

Comments
 (0)