/ src / Ryujinx.Common / SystemInterop / StdErrAdapter.cs
StdErrAdapter.cs
  1  using Microsoft.Win32.SafeHandles;
  2  using Ryujinx.Common.Logging;
  3  using System;
  4  using System.IO;
  5  using System.Runtime.InteropServices;
  6  using System.Runtime.Versioning;
  7  using System.Threading;
  8  using System.Threading.Tasks;
  9  
 10  namespace Ryujinx.Common.SystemInterop
 11  {
 12      public partial class StdErrAdapter : IDisposable
 13      {
 14          private bool _disposable;
 15          private Stream _pipeReader;
 16          private Stream _pipeWriter;
 17          private CancellationTokenSource _cancellationTokenSource;
 18          private Task _worker;
 19  
 20          public StdErrAdapter()
 21          {
 22              if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
 23              {
 24                  RegisterPosix();
 25              }
 26          }
 27  
 28          [SupportedOSPlatform("linux")]
 29          [SupportedOSPlatform("macos")]
 30          private void RegisterPosix()
 31          {
 32              const int StdErrFileno = 2;
 33  
 34              (int readFd, int writeFd) = MakePipe();
 35              dup2(writeFd, StdErrFileno);
 36  
 37              _pipeReader = CreateFileDescriptorStream(readFd);
 38              _pipeWriter = CreateFileDescriptorStream(writeFd);
 39  
 40              _cancellationTokenSource = new CancellationTokenSource();
 41              _worker = Task.Run(async () => await EventWorkerAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
 42              _disposable = true;
 43          }
 44  
 45          [SupportedOSPlatform("linux")]
 46          [SupportedOSPlatform("macos")]
 47          private async Task EventWorkerAsync(CancellationToken cancellationToken)
 48          {
 49              using TextReader reader = new StreamReader(_pipeReader, leaveOpen: true);
 50              string line;
 51              while (cancellationToken.IsCancellationRequested == false && (line = await reader.ReadLineAsync(cancellationToken)) != null)
 52              {
 53                  Logger.Error?.PrintRawMsg(line);
 54              }
 55          }
 56  
 57          public void Dispose()
 58          {
 59              GC.SuppressFinalize(this);
 60  
 61              if (_disposable)
 62              {
 63                  _disposable = false;
 64  
 65                  if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
 66                  {
 67                      _cancellationTokenSource.Cancel();
 68                      _worker.Wait(0);
 69                      _pipeReader?.Close();
 70                      _pipeWriter?.Close();
 71                  }
 72              }
 73          }
 74  
 75          [LibraryImport("libc", SetLastError = true)]
 76          private static partial int dup2(int fd, int fd2);
 77  
 78          [LibraryImport("libc", SetLastError = true)]
 79          private static partial int pipe(Span<int> pipefd);
 80  
 81          private static (int, int) MakePipe()
 82          {
 83              Span<int> pipefd = stackalloc int[2];
 84  
 85              if (pipe(pipefd) == 0)
 86              {
 87                  return (pipefd[0], pipefd[1]);
 88              }
 89  
 90              throw new();
 91          }
 92  
 93          [SupportedOSPlatform("linux")]
 94          [SupportedOSPlatform("macos")]
 95          private static Stream CreateFileDescriptorStream(int fd)
 96          {
 97              return new FileStream(
 98                  new SafeFileHandle(fd, ownsHandle: true),
 99                  FileAccess.ReadWrite
100              );
101          }
102  
103      }
104  }