aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Common/SystemInterop
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Common/SystemInterop')
-rw-r--r--Ryujinx.Common/SystemInterop/StdErrAdapter.cs93
-rw-r--r--Ryujinx.Common/SystemInterop/UnixStream.cs155
2 files changed, 248 insertions, 0 deletions
diff --git a/Ryujinx.Common/SystemInterop/StdErrAdapter.cs b/Ryujinx.Common/SystemInterop/StdErrAdapter.cs
new file mode 100644
index 00000000..12e240ad
--- /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 00000000..1d644997
--- /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;
+ }
+ }
+}