From 669d33183e3ea5b2d8f1258d7dcffd44fc4e4842 Mon Sep 17 00:00:00 2001 From: mahee96 <47920326+mahee96@users.noreply.github.com> Date: Wed, 30 Jul 2025 23:35:16 +0530 Subject: [PATCH] - [ConsoleLogger]: Fix race conditions during shutdown and possible crashes by writing to closed stream handler --- SideStore/Utils/iostreams/ConsoleLogger.swift | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/SideStore/Utils/iostreams/ConsoleLogger.swift b/SideStore/Utils/iostreams/ConsoleLogger.swift index 479cc5c8..5c086e4f 100644 --- a/SideStore/Utils/iostreams/ConsoleLogger.swift +++ b/SideStore/Utils/iostreams/ConsoleLogger.swift @@ -59,48 +59,64 @@ public class AbstractConsoleLogger: ConsoleLogger{ originalStdout = dup(STDOUT_FILENO) originalStderr = dup(STDERR_FILENO) + let redirectedOutStream = self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1 + let redirectedErrStream = self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1 + // Redirect stdout and stderr to our pipes - dup2(self.outPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDOUT_FILENO) - dup2(self.errPipe?.fileHandleForWriting.fileDescriptor ?? -1, STDERR_FILENO) + dup2(redirectedOutStream, STDOUT_FILENO) + dup2(redirectedErrStream, STDERR_FILENO) + // Disable libc-level buffering + // (libc by default uses bufferring except its own console/TTYs such as for pipes) + // we do have our own buffering so we disable stdlib io level bufferring + setvbuf(stdout, nil, _IONBF, 0) // disable buffering for stdout + setvbuf(stderr, nil, _IONBF, 0) // disable buffering for stderr + // Setup readability handlers for raw data setupReadabilityHandler(for: outputHandle, isError: false) setupReadabilityHandler(for: errorHandle, isError: true) } + let shutdownLock = NSLock() + private func setupReadabilityHandler(for handle: FileHandle?, isError: Bool) { - handle?.readabilityHandler = { [weak self] handle in - let data = handle.availableData - if !data.isEmpty { - self?.writeQueue.async { - try? self?.writeData(data) - } - - // Forward to original std stream - if let originalFD = isError ? self?.originalStderr : self?.originalStdout { - data.withUnsafeBytes { (bufferPointer) -> Void in - if let baseAddress = bufferPointer.baseAddress, bufferPointer.count > 0 { - write(originalFD, baseAddress, bufferPointer.count) - } - } - } + handle?.readabilityHandler = readHandler(for: handle, isError: isError) + } + + private func readHandler(for handle: FileHandle?, isError: Bool) -> (FileHandle) -> Void { + return { [weak self] _ in + guard let self, let data = handle?.availableData else { return } + + shutdownLock.lock() + defer { shutdownLock.unlock() } + + writeQueue.async { + try? self.writeData(data) + } + + if let fd = isError ? self.originalStderr : self.originalStdout { + data.withUnsafeBytes { _ = write(fd, $0.baseAddress, $0.count) } } } } + func writeData(_ data: Data) throws { throw AbstractClassError.abstractMethodInvoked } func stopCapturing() { + shutdownLock.lock() + defer { shutdownLock.unlock() } + ostream.close() // Restore original stdout and stderr - if let stdout = originalStdout { + if let stdout = originalStdout, stdout != STDOUT_FILENO { dup2(stdout, STDOUT_FILENO) close(stdout) } - if let stderr = originalStderr { + if let stderr = originalStderr, stderr != STDERR_FILENO { dup2(stderr, STDERR_FILENO) close(stderr) }