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