From 9b1cc2cec6135602efc5dc5afa45ed3db261eb42 Mon Sep 17 00:00:00 2001 From: merry Date: Sat, 25 Feb 2023 15:07:23 +0000 Subject: [PATCH] Logging: Redirect StdErr into logging system (#4427) * Logging: Redirect StdErr into logging system * Remove Mono.Unix * Apply suggestions from code review Co-authored-by: riperiperi * Address comments --------- Co-authored-by: Mary Co-authored-by: riperiperi Co-authored-by: Mary --- Directory.Packages.props | 2 +- Ryujinx.Common/Logging/Logger.cs | 15 +- Ryujinx.Common/SystemInterop/StdErrAdapter.cs | 93 +++++++++++ Ryujinx.Common/SystemInterop/UnixStream.cs | 155 ++++++++++++++++++ 4 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 Ryujinx.Common/SystemInterop/StdErrAdapter.cs create mode 100644 Ryujinx.Common/SystemInterop/UnixStream.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index ae05ff54c..35c98e5a3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,4 +52,4 @@ - \ No newline at end of file + diff --git a/Ryujinx.Common/Logging/Logger.cs b/Ryujinx.Common/Logging/Logger.cs index c1abdba9b..4d48dd48d 100644 --- a/Ryujinx.Common/Logging/Logger.cs +++ b/Ryujinx.Common/Logging/Logger.cs @@ -1,3 +1,4 @@ +using Ryujinx.Common.SystemInterop; using System; using System.Collections.Generic; using System.Diagnostics; @@ -14,6 +15,8 @@ namespace Ryujinx.Common.Logging private static readonly List m_LogTargets; + private static readonly StdErrAdapter _stdErrAdapter; + public static event EventHandler Updated; public readonly struct Log @@ -77,7 +80,13 @@ namespace Ryujinx.Common.Logging { Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, "Stubbed. " + message), data)); } - } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PrintRawMsg(string message) + { + Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, message)); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static string FormatMessage(LogClass Class, string Caller, string Message) => $"{Class} {Caller}: {Message}"; @@ -119,6 +128,8 @@ namespace Ryujinx.Common.Logging Warning = new Log(LogLevel.Warning); Info = new Log(LogLevel.Info); Trace = new Log(LogLevel.Trace); + + _stdErrAdapter = new StdErrAdapter(); } public static void RestartTime() @@ -164,6 +175,8 @@ namespace Ryujinx.Common.Logging { Updated = null; + _stdErrAdapter.Dispose(); + foreach (var target in m_LogTargets) { target.Dispose(); diff --git a/Ryujinx.Common/SystemInterop/StdErrAdapter.cs b/Ryujinx.Common/SystemInterop/StdErrAdapter.cs new file mode 100644 index 000000000..12e240ad3 --- /dev/null +++ b/Ryujinx.Common/SystemInterop/StdErrAdapter.cs @@ -0,0 +1,93 @@ +using System; +using System.IO; +using System.Runtime.Versioning; +using System.Threading; +using Ryujinx.Common.Logging; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common.SystemInterop +{ + public partial class StdErrAdapter : IDisposable + { + private bool _disposable = false; + private UnixStream _pipeReader; + private UnixStream _pipeWriter; + private Thread _worker; + + public StdErrAdapter() + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + RegisterPosix(); + } + } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private void RegisterPosix() + { + const int stdErrFileno = 2; + + (int readFd, int writeFd) = MakePipe(); + dup2(writeFd, stdErrFileno); + + _pipeReader = new UnixStream(readFd); + _pipeWriter = new UnixStream(writeFd); + + _worker = new Thread(EventWorker); + _disposable = true; + _worker.Start(); + } + + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + private void EventWorker() + { + TextReader reader = new StreamReader(_pipeReader); + string line; + while ((line = reader.ReadLine()) != null) + { + Logger.Error?.PrintRawMsg(line); + } + } + + private void Dispose(bool disposing) + { + if (_disposable) + { + if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + _pipeReader?.Close(); + _pipeWriter?.Close(); + } + + _disposable = false; + } + } + + public void Dispose() + { + Dispose(true); + } + + [LibraryImport("libc", SetLastError = true)] + private static partial int dup2(int fd, int fd2); + + [LibraryImport("libc", SetLastError = true)] + private static unsafe partial int pipe(int* pipefd); + + private static unsafe (int, int) MakePipe() + { + int *pipefd = stackalloc int[2]; + + if (pipe(pipefd) == 0) + { + return (pipefd[0], pipefd[1]); + } + else + { + throw new(); + } + } + } +} diff --git a/Ryujinx.Common/SystemInterop/UnixStream.cs b/Ryujinx.Common/SystemInterop/UnixStream.cs new file mode 100644 index 000000000..1d6449974 --- /dev/null +++ b/Ryujinx.Common/SystemInterop/UnixStream.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; + +namespace Ryujinx.Common.SystemInterop +{ + [SupportedOSPlatform("linux")] + [SupportedOSPlatform("macos")] + public partial class UnixStream : Stream, IDisposable + { + private const int InvalidFd = -1; + + private int _fd; + + [LibraryImport("libc", SetLastError = true)] + private static partial long read(int fd, IntPtr buf, ulong count); + + [LibraryImport("libc", SetLastError = true)] + private static partial long write(int fd, IntPtr buf, ulong count); + + [LibraryImport("libc", SetLastError = true)] + private static partial int close(int fd); + + public UnixStream(int fd) + { + if (InvalidFd == fd) + { + throw new ArgumentException("Invalid file descriptor"); + } + + _fd = fd; + + CanRead = read(fd, IntPtr.Zero, 0) != -1; + CanWrite = write(fd, IntPtr.Zero, 0) != -1; + } + + ~UnixStream() + { + Close(); + } + + public override bool CanRead { get; } + public override bool CanWrite { get; } + public override bool CanSeek => false; + + public override long Length => throw new NotSupportedException(); + + public override long Position + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public override void Flush() + { + } + + public override unsafe int Read([In, Out] byte[] buffer, int offset, int count) + { + if (offset < 0 || offset > (buffer.Length - count) || count < 0) + { + throw new ArgumentOutOfRangeException(); + } + + if (buffer.Length == 0) + { + return 0; + } + + long r = 0; + fixed (byte* buf = &buffer[offset]) + { + do + { + r = read(_fd, (IntPtr)buf, (ulong)count); + } while (ShouldRetry(r)); + } + + return (int)r; + } + + public override unsafe void Write(byte[] buffer, int offset, int count) + { + if (offset < 0 || offset > (buffer.Length - count) || count < 0) + { + throw new ArgumentOutOfRangeException(); + } + + if (buffer.Length == 0) + { + return; + } + + fixed (byte* buf = &buffer[offset]) + { + long r = 0; + do { + r = write(_fd, (IntPtr)buf, (ulong)count); + } while (ShouldRetry(r)); + } + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } + + public override void SetLength(long value) + { + throw new NotSupportedException(); + } + + public override void Close() + { + if (_fd == InvalidFd) + { + return; + } + + Flush(); + + int r; + do { + r = close(_fd); + } while (ShouldRetry(r)); + + _fd = InvalidFd; + } + + void IDisposable.Dispose() + { + Close(); + } + + private bool ShouldRetry(long r) + { + if (r == -1) + { + const int eintr = 4; + + int errno = Marshal.GetLastPInvokeError(); + + if (errno == eintr) + { + return true; + } + + throw new SystemException($"Operation failed with error 0x{errno:X}"); + } + + return false; + } + } +}