osdep/io: use mp_fopen on all platforms

Currently, all fopen uses in mpv do not set cloexec flag, resulting
in fd leak whenever mpv creates a process.

This makes mpv use mp_fopen on all platforms, and always set O_CLOEXEC
on the opened fd. This is required because the "e" flag for fopen is
nonstandard and UB in C11, and unsupported on macOS.

This had been done similarly in other projects. e.g.
https://gitlab.com/graphviz/graphviz/-/merge_requests/3797
This commit is contained in:
nanahi authored and Kacper Michajłow committed 2026-06-04 04:58:16 +02:00
1 parent 6d5c8591eb
commit f1a42eff01
2 files changed
+56 -54

No files matched your search

+53 -52
View File
@@ -62,6 +62,59 @@ bool mp_set_cloexec(int fd)
return true;
}
// fopen that sets cloexec.
FILE *mp_fopen(const char *filename, const char *mode)
{
if (!mode[0]) {
errno = EINVAL;
return NULL;
}
int rwmode;
int oflags = 0;
switch (mode[0]) {
case 'r':
rwmode = O_RDONLY;
break;
case 'w':
rwmode = O_WRONLY;
oflags |= O_CREAT | O_TRUNC;
break;
case 'a':
rwmode = O_WRONLY;
oflags |= O_CREAT | O_APPEND;
break;
default:
errno = EINVAL;
return NULL;
}
// Parse extra mode flags
for (const char *pos = mode + 1; *pos; pos++) {
switch (*pos) {
case '+': rwmode = O_RDWR; break;
case 'x': oflags |= O_EXCL; break;
// Ignore unknown flags (glibc does too)
default: break;
}
}
// Open a CRT file descriptor
int fd = open(filename, rwmode | oflags | O_CLOEXEC, 0600);
if (fd < 0)
return NULL;
// Add 'b' to the mode so the CRT knows the file is opened in binary mode
char bmode[] = { mode[0], 'b', rwmode == O_RDWR ? '+' : '\0', '\0' };
FILE *fp = fdopen(fd, bmode);
if (!fp) {
close(fd);
return NULL;
}
return fp;
}
#ifndef _WIN32
int mp_dup_cloexec(int fd)
{
@@ -508,58 +561,6 @@ int mp_rename(const char *oldpath, const char *newpath)
return 0;
}
FILE *mp_fopen(const char *filename, const char *mode)
{
if (!mode[0]) {
errno = EINVAL;
return NULL;
}
int rwmode;
int oflags = 0;
switch (mode[0]) {
case 'r':
rwmode = _O_RDONLY;
break;
case 'w':
rwmode = _O_WRONLY;
oflags |= _O_CREAT | _O_TRUNC;
break;
case 'a':
rwmode = _O_WRONLY;
oflags |= _O_CREAT | _O_APPEND;
break;
default:
errno = EINVAL;
return NULL;
}
// Parse extra mode flags
for (const char *pos = mode + 1; *pos; pos++) {
switch (*pos) {
case '+': rwmode = _O_RDWR; break;
case 'x': oflags |= _O_EXCL; break;
// Ignore unknown flags (glibc does too)
default: break;
}
}
// Open a CRT file descriptor
int fd = mp_open(filename, rwmode | oflags);
if (fd < 0)
return NULL;
// Add 'b' to the mode so the CRT knows the file is opened in binary mode
char bmode[] = { mode[0], 'b', rwmode == _O_RDWR ? '+' : '\0', '\0' };
FILE *fp = fdopen(fd, bmode);
if (!fp) {
close(fd);
return NULL;
}
return fp;
}
// Windows' MAX_PATH/PATH_MAX/FILENAME_MAX is fixed to 260, but this limit
// applies to unicode paths encoded with wchar_t (2 bytes on Windows). The UTF-8
// version could end up bigger in memory. In the worst case each wchar_t is
+3 -2
View File
@@ -80,6 +80,9 @@ int mp_make_cloexec_pipe(int pipes[2]);
int mp_make_wakeup_pipe(int pipes[2]);
void mp_flush_wakeup_pipe(int pipe_end);
FILE *mp_fopen(const char *filename, const char *mode);
#define fopen(...) mp_fopen(__VA_ARGS__)
#ifdef _WIN32
#include <wchar.h>
@@ -117,7 +120,6 @@ int mp_fprintf(FILE *stream, const char *format, ...) MP_PRINTF_ATTRIBUTE(2, 3);
int mp_open(const char *filename, int oflag, ...);
int mp_creat(const char *filename, int mode);
int mp_rename(const char *oldpath, const char *newpath);
FILE *mp_fopen(const char *filename, const char *mode);
DIR *mp_opendir(const char *path);
struct dirent *mp_readdir(DIR *dir);
int mp_closedir(DIR *dir);
@@ -190,7 +192,6 @@ void mp_globfree(mp_glob_t *pglob);
#define open(...) mp_open(__VA_ARGS__)
#define creat(...) mp_creat(__VA_ARGS__)
#define rename(...) mp_rename(__VA_ARGS__)
#define fopen(...) mp_fopen(__VA_ARGS__)
#define opendir(...) mp_opendir(__VA_ARGS__)
#define readdir(...) mp_readdir(__VA_ARGS__)
#define closedir(...) mp_closedir(__VA_ARGS__)