1 // 2 // Copyright (c) 2010-2018 Antmicro 3 // Copyright (c) 2011-2015 Realtime Embedded 4 // 5 // This file is licensed under the MIT License. 6 // Full license text is available in 'licenses/MIT.txt'. 7 // 8 #if !PLATFORM_WINDOWS 9 using System; 10 using Mono.Unix.Native; 11 using Mono.Unix; 12 using System.Runtime.InteropServices; 13 using System.IO; 14 using Antmicro.Migrant; 15 using Antmicro.Migrant.Hooks; 16 using System.Threading; 17 using System.Threading.Tasks; 18 19 namespace Antmicro.Renode.Utilities 20 { 21 public class PtyUnixStream : Stream 22 { PtyUnixStream()23 public PtyUnixStream() 24 { 25 Init(); 26 } 27 ReadByte()28 public override int ReadByte() 29 { 30 try 31 { 32 if(ReadTimeout > 0) 33 { 34 var result = IsDataAvailable(ReadTimeout) ? base.ReadByte() : -2; 35 ReadTimeout = -1; 36 return result; 37 } 38 else 39 { 40 return WaitUntilDataIsAvailable() ? base.ReadByte() : -1; 41 } 42 } 43 catch(IOException) 44 { 45 return -1; 46 } 47 } 48 Flush()49 public override void Flush() 50 { 51 Stream.Flush(); 52 } 53 ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)54 public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) 55 { 56 return Stream.ReadAsync(buffer, offset, count, cancellationToken); 57 } 58 Read(byte[] buffer, int offset, int count)59 public override int Read(byte[] buffer, int offset, int count) 60 { 61 return Stream.Read(buffer, offset, count); 62 } 63 Seek(long offset, SeekOrigin origin)64 public override long Seek(long offset, SeekOrigin origin) 65 { 66 return Stream.Seek(offset, origin); 67 } 68 SetLength(long value)69 public override void SetLength(long value) 70 { 71 Stream.SetLength(value); 72 } 73 Write(byte[] buffer, int offset, int count)74 public override void Write(byte[] buffer, int offset, int count) 75 { 76 Stream.Write(buffer, offset, count); 77 } 78 79 public override bool CanRead { get { return Stream.CanRead; } } 80 81 public override bool CanSeek { get { return Stream.CanSeek; } } 82 83 public override bool CanWrite { get { return Stream.CanWrite; } } 84 85 public override long Length { get { return Stream.Length; } } 86 87 public override bool CanTimeout { get { return true; } } 88 89 public override int ReadTimeout { get; set; } 90 91 public override long Position 92 { 93 get { return Stream.Position; } 94 set { Stream.Position = value; } 95 } 96 97 public string SlaveName 98 { 99 get { return slaveName; } 100 private set { slaveName = value; } 101 } 102 103 public int SlaveFd { get { return slaveFd; } } 104 Dispose(bool disposing)105 protected override void Dispose(bool disposing) 106 { 107 // masterFd will be closed by disposing the base 108 base.Dispose(disposing); 109 Syscall.close(slaveFd); 110 disposed = true; 111 } 112 OpenNewSlavePty(out int masterFd, out int slaveFd)113 private static string OpenNewSlavePty(out int masterFd, out int slaveFd) 114 { 115 var amaster = Marshal.AllocHGlobal(4); 116 var aslave = Marshal.AllocHGlobal(4); 117 var name = Marshal.AllocHGlobal(1024); 118 119 IntPtr termios = Marshal.AllocHGlobal(128); // termios struct is 60-bytes, but we allocate more just to make sure 120 Tcgetattr(0, termios); 121 Cfmakeraw(termios); 122 123 int result = Openpty(amaster, aslave, name, termios, IntPtr.Zero); 124 UnixMarshal.ThrowExceptionForLastErrorIf(result); 125 126 masterFd = Marshal.ReadInt32(amaster); 127 slaveFd = Marshal.ReadInt32(aslave); 128 var slaveName = Marshal.PtrToStringAnsi(name); 129 130 Marshal.FreeHGlobal(amaster); 131 Marshal.FreeHGlobal(aslave); 132 Marshal.FreeHGlobal(name); 133 Marshal.FreeHGlobal(termios); 134 135 var gptResult = Grantpt(masterFd); 136 UnixMarshal.ThrowExceptionForLastErrorIf(gptResult); 137 var uptResult = Unlockpt(masterFd); 138 UnixMarshal.ThrowExceptionForLastErrorIf(uptResult); 139 140 return slaveName; 141 } 142 WaitUntilDataIsAvailable()143 private bool WaitUntilDataIsAvailable() 144 { 145 int pollResult; 146 bool retry; 147 var pollData = new[] { new Pollfd { fd = masterFd, events = PollEvents.POLLIN } }; 148 do 149 { 150 retry = false; 151 pollResult = Syscall.poll(pollData, -1); 152 // here we compare flag using == operator as we want only POLLHUP to 153 // activate the condition 154 if(pollResult == 1 && pollData[0].revents == PollEvents.POLLHUP) 155 { 156 // this is necessary as poll will result with PollHup when 157 // client disconnects from slave tty; we want to allow to 158 // connect again 159 System.Threading.Thread.Sleep(HangUpCheckPeriod); 160 retry = true; 161 } 162 } 163 while(!disposed && (retry || UnixMarshal.ShouldRetrySyscall(pollResult))); 164 // here we don't use simple == operator to detect POLLIN, as it turns out 165 // that POLLHUP is quite sticky - once it is reported it stays forever 166 return pollResult == 1 && (pollData[0].revents & PollEvents.POLLIN) != 0; 167 } 168 IsDataAvailable(int timeout, out int pollResult)169 private bool IsDataAvailable(int timeout, out int pollResult) 170 { 171 var pollData = new[] { new Pollfd { fd = masterFd, events = PollEvents.POLLIN } }; 172 do 173 { 174 pollResult = Syscall.poll(pollData, timeout); 175 } 176 while(!disposed && UnixMarshal.ShouldRetrySyscall(pollResult)); 177 return pollResult > 0; 178 } 179 IsDataAvailable(int timeout, bool throwOnError = true)180 private bool IsDataAvailable(int timeout, bool throwOnError = true) 181 { 182 int pollResult; 183 IsDataAvailable(timeout, out pollResult); 184 if(throwOnError && pollResult < 0) 185 { 186 UnixMarshal.ThrowExceptionForLastError(); 187 } 188 return pollResult > 0; 189 } 190 191 [PostDeserialization] Init()192 private void Init() 193 { 194 SlaveName = OpenNewSlavePty(out masterFd, out slaveFd); 195 } 196 197 private UnixStream Stream 198 { 199 get 200 { 201 if(stream == null) 202 { 203 stream = new UnixStream(masterFd, true); 204 } 205 return stream; 206 } 207 } 208 209 [DllImport("libc", EntryPoint = "getpt")] Getpt()210 private extern static int Getpt(); 211 212 [DllImport("libc", EntryPoint = "grantpt")] Grantpt(int fd)213 private extern static int Grantpt(int fd); 214 215 [DllImport("libc", EntryPoint = "unlockpt")] Unlockpt(int fd)216 private extern static int Unlockpt(int fd); 217 218 [DllImport("libc", EntryPoint="ptsname")] Ptsname(int fd)219 extern static IntPtr Ptsname(int fd); 220 221 [DllImport("libc", EntryPoint = "cfmakeraw")] Cfmakeraw(IntPtr termios)222 private extern static void Cfmakeraw(IntPtr termios); // TODO: this is non-posix, but should work on BSD 223 224 [DllImport("libc", EntryPoint = "tcgetattr")] Tcgetattr(int fd, IntPtr termios)225 private extern static void Tcgetattr(int fd, IntPtr termios); 226 227 [DllImport("libc", EntryPoint = "tcsetattr")] Tcsetattr(int fd, int attr, IntPtr termios)228 private extern static void Tcsetattr(int fd, int attr, IntPtr termios); 229 230 #if PLATFORM_LINUX 231 [DllImport("libutil.so.1", EntryPoint = "openpty")] 232 #else 233 [DllImport("util", EntryPoint = "openpty")] 234 #endif Openpty(IntPtr amaster, IntPtr aslave, IntPtr name, IntPtr termp, IntPtr winp)235 private extern static int Openpty(IntPtr amaster, IntPtr aslave, IntPtr name, IntPtr termp, IntPtr winp); 236 237 [Transient] 238 private UnixStream stream; 239 240 [Transient] 241 private string slaveName; 242 243 [Transient] 244 private int masterFd; 245 246 [Transient] 247 private int slaveFd; 248 249 private bool disposed; 250 251 private const int HangUpCheckPeriod = 500; 252 } 253 } 254 #endif 255