From f1a42eff0171d32f928477968b2a7d872f55f35b Mon Sep 17 00:00:00 2001 From: nanahi <130121847+na-na-hi@users.noreply.github.com> Date: Tue, 24 Mar 2026 22:14:39 -0400 Subject: [PATCH] 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 --- osdep/io.c | 105 +++++++++++++++++++++++++++-------------------------- osdep/io.h | 5 ++- 2 files changed, 56 insertions(+), 54 deletions(-) diff --git a/osdep/io.c b/osdep/io.c index 9401a46805..b6bf3cd766 100644 --- a/osdep/io.c +++ b/osdep/io.c @@ -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 diff --git a/osdep/io.h b/osdep/io.h index 1ab7a7f248..bb97733932 100644 --- a/osdep/io.h +++ b/osdep/io.h @@ -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 @@ -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__)