diff --git a/Userland/Libraries/LibC/bits/mutex_locker.h b/Userland/Libraries/LibC/bits/mutex_locker.h new file mode 100644 index 0000000000..e3771c3818 --- /dev/null +++ b/Userland/Libraries/LibC/bits/mutex_locker.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021, Daniel Bertalan + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +// We don't want to bring LibThreading headers into LibC, so we use plain +// pthread mutexes and this RAII guard. +namespace LibC { + +class [[nodiscard]] MutexLocker { +public: + explicit MutexLocker(pthread_mutex_t& mutex) + : m_mutex(mutex) + { + lock(); + } + + ~MutexLocker() + { + unlock(); + } + + void lock() { pthread_mutex_lock(&m_mutex); } + void unlock() { pthread_mutex_unlock(&m_mutex); } + +private: + pthread_mutex_t& m_mutex; +}; + +} diff --git a/Userland/Libraries/LibC/bits/stdio_file_implementation.h b/Userland/Libraries/LibC/bits/stdio_file_implementation.h index 48029992ad..98e4820dcf 100644 --- a/Userland/Libraries/LibC/bits/stdio_file_implementation.h +++ b/Userland/Libraries/LibC/bits/stdio_file_implementation.h @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -127,6 +128,10 @@ private: pid_t m_popen_child { -1 }; Buffer m_buffer; __pthread_mutex_t m_mutex; + IntrusiveListNode m_list_node; + +public: + using List = IntrusiveList<&FILE::m_list_node>; }; class ScopedFileLock { diff --git a/Userland/Libraries/LibC/stdio.cpp b/Userland/Libraries/LibC/stdio.cpp index 861e20e5ec..4fbd70e8aa 100644 --- a/Userland/Libraries/LibC/stdio.cpp +++ b/Userland/Libraries/LibC/stdio.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +27,15 @@ #include #include +static constinit pthread_mutex_t s_open_streams_lock = __PTHREAD_MUTEX_INITIALIZER; + +// The list of open files is initialized in __stdio_init. +// We cannot rely on global constructors to initialize it, because it must +// be initialized before other global constructors run. Similarly, we cannot +// allow global destructors to destruct it. +alignas(FILE::List) static u8 s_open_streams_storage[sizeof(FILE::List)]; +static FILE::List* const s_open_streams = reinterpret_cast(s_open_streams_storage); + FILE::~FILE() { bool already_closed = m_fd == -1; @@ -34,9 +44,13 @@ FILE::~FILE() FILE* FILE::create(int fd, int mode) { - void* file = calloc(1, sizeof(FILE)); - new (file) FILE(fd, mode); - return (FILE*)file; + void* file_location = calloc(1, sizeof(FILE)); + if (file_location == nullptr) + return nullptr; + auto* file = new (file_location) FILE(fd, mode); + LibC::MutexLocker locker(s_open_streams_lock); + s_open_streams->append(*file); + return file; } bool FILE::close() @@ -517,10 +531,14 @@ FILE* stderr = reinterpret_cast(&default_streams[2]); void __stdio_init() { + new (s_open_streams) FILE::List(); new (stdin) FILE(0, O_RDONLY); new (stdout) FILE(1, O_WRONLY); new (stderr) FILE(2, O_WRONLY); stderr->setbuf(nullptr, _IONBF, 0); + s_open_streams->append(*stdin); + s_open_streams->append(*stdout); + s_open_streams->append(*stderr); __stdio_is_initialized = true; } @@ -568,8 +586,13 @@ int feof(FILE* stream) int fflush(FILE* stream) { if (!stream) { - dbgln("FIXME: fflush(nullptr) should flush all open streams"); - return 0; + int rc = 0; + LibC::MutexLocker locker(s_open_streams_lock); + for (auto& file : *s_open_streams) { + ScopedFileLock lock(&file); + rc = file.flush() ? rc : EOF; + } + return rc; } ScopedFileLock lock(stream); return stream->flush() ? 0 : EOF; @@ -1070,6 +1093,10 @@ int fclose(FILE* stream) } ScopedValueRollback errno_restorer(errno); + { + LibC::MutexLocker locker(s_open_streams_lock); + s_open_streams->remove(*stream); + } stream->~FILE(); if (!is_default_stream(stream)) free(stream);