1 /*
2  * PROJECT:     ReactOS Automatic Testing Utility
3  * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4  * PURPOSE:     Class that manages an unidirectional anonymous byte stream pipe
5  * COPYRIGHT:   Copyright 2015 Thomas Faber (thomas.faber@reactos.org)
6  *              Copyright 2019 Colin Finck (colin@reactos.org)
7  */
8 
9 #include "precomp.h"
10 
11 LONG CPipe::m_lPipeCount = 0;
12 
13 
14 /**
15  * Constructs a CPipe object and initializes read and write handles.
16  */
CPipe()17 CPipe::CPipe()
18 {
19     SECURITY_ATTRIBUTES SecurityAttributes;
20 
21     SecurityAttributes.nLength = sizeof(SecurityAttributes);
22     SecurityAttributes.bInheritHandle = TRUE;
23     SecurityAttributes.lpSecurityDescriptor = NULL;
24 
25     // Construct a unique pipe name.
26     WCHAR wszPipeName[MAX_PATH];
27     InterlockedIncrement(&m_lPipeCount);
28     swprintf(wszPipeName, L"\\\\.\\pipe\\TestmanPipe%ld", m_lPipeCount);
29 
30     // Create a named pipe with the default settings, but overlapped (asynchronous) operations.
31     // Latter feature is why we can't simply use CreatePipe.
32     const DWORD dwDefaultBufferSize = 4096;
33     const DWORD dwDefaultTimeoutMilliseconds = 120000;
34 
35     m_hReadPipe = CreateNamedPipeW(wszPipeName,
36         PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
37         PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
38         1,
39         dwDefaultBufferSize,
40         dwDefaultBufferSize,
41         dwDefaultTimeoutMilliseconds,
42         &SecurityAttributes);
43     if (m_hReadPipe == INVALID_HANDLE_VALUE)
44     {
45         FATAL("CreateNamedPipe failed\n");
46     }
47 
48     // Use CreateFileW to get the write handle to the pipe.
49     // Writing is done synchronously, so no FILE_FLAG_OVERLAPPED here!
50     m_hWritePipe = CreateFileW(wszPipeName,
51         GENERIC_WRITE,
52         0,
53         &SecurityAttributes,
54         OPEN_EXISTING,
55         FILE_ATTRIBUTE_NORMAL,
56         NULL);
57     if (m_hWritePipe == INVALID_HANDLE_VALUE)
58     {
59         FATAL("CreateFileW failed\n");
60     }
61 
62     // Prepare the OVERLAPPED structure for reading.
63     ZeroMemory(&m_ReadOverlapped, sizeof(m_ReadOverlapped));
64     m_ReadOverlapped.hEvent = CreateEventW(NULL, TRUE, TRUE, NULL);
65     if (!m_ReadOverlapped.hEvent)
66     {
67         FATAL("CreateEvent failed\n");
68     }
69 }
70 
71 /**
72  * Destructs a CPipe object and closes all open handles.
73  */
~CPipe()74 CPipe::~CPipe()
75 {
76     if (m_hReadPipe)
77         CloseHandle(m_hReadPipe);
78     if (m_hWritePipe)
79         CloseHandle(m_hWritePipe);
80 }
81 
82 /**
83  * Closes a CPipe's read pipe, leaving the write pipe unchanged.
84  */
85 void
CloseReadPipe()86 CPipe::CloseReadPipe()
87 {
88     if (!m_hReadPipe)
89         FATAL("Trying to close already closed read pipe\n");
90     CloseHandle(m_hReadPipe);
91     m_hReadPipe = NULL;
92 }
93 
94 /**
95  * Closes a CPipe's write pipe, leaving the read pipe unchanged.
96  */
97 void
CloseWritePipe()98 CPipe::CloseWritePipe()
99 {
100     if (!m_hWritePipe)
101         FATAL("Trying to close already closed write pipe\n");
102     CloseHandle(m_hWritePipe);
103     m_hWritePipe = NULL;
104 }
105 
106 /**
107  * Reads data from a pipe without advancing the read offset and/or retrieves information about available data.
108  *
109  * This function must not be called after CloseReadPipe.
110  *
111  * @param Buffer
112  * An optional buffer to read pipe data into.
113  *
114  * @param BufferSize
115  * The size of the buffer specified in Buffer, or 0 if no read should be performed.
116  *
117  * @param BytesRead
118  * On return, the number of bytes actually read from the pipe into Buffer.
119  *
120  * @param TotalBytesAvailable
121  * On return, the total number of bytes available to read from the pipe.
122  *
123  * @return
124  * True on success, false on failure; call GetLastError for error information.
125  *
126  * @see PeekNamedPipe
127  */
128 bool
Peek(PVOID Buffer,DWORD BufferSize,PDWORD BytesRead,PDWORD TotalBytesAvailable)129 CPipe::Peek(PVOID Buffer, DWORD BufferSize, PDWORD BytesRead, PDWORD TotalBytesAvailable)
130 {
131     if (!m_hReadPipe)
132         FATAL("Trying to peek from a closed read pipe\n");
133 
134     return PeekNamedPipe(m_hReadPipe, Buffer, BufferSize, BytesRead, TotalBytesAvailable, NULL);
135 }
136 
137 /**
138  * Reads data from the read pipe, advancing the read offset accordingly.
139  *
140  * This function must not be called after CloseReadPipe.
141  *
142  * @param Buffer
143  * Buffer to read pipe data into.
144  *
145  * @param NumberOfBytesToRead
146  * The number of bytes to read into Buffer.
147  *
148  * @param NumberOfBytesRead
149  * On return, the number of bytes actually read from the pipe into Buffer.
150  *
151  * @return
152  * Returns a Win32 error code. Expected error codes include:
153  *   - ERROR_SUCCESS:     The read has completed successfully.
154  *   - WAIT_TIMEOUT:      The given timeout has elapsed before any data was read.
155  *   - ERROR_BROKEN_PIPE: The other end of the pipe has been closed.
156  *
157  * @see ReadFile
158  */
159 DWORD
Read(PVOID Buffer,DWORD NumberOfBytesToRead,PDWORD NumberOfBytesRead,DWORD TimeoutMilliseconds)160 CPipe::Read(PVOID Buffer, DWORD NumberOfBytesToRead, PDWORD NumberOfBytesRead, DWORD TimeoutMilliseconds)
161 {
162     if (!m_hReadPipe)
163     {
164         FATAL("Trying to read from a closed read pipe\n");
165     }
166 
167     if (ReadFile(m_hReadPipe, Buffer, NumberOfBytesToRead, NumberOfBytesRead, &m_ReadOverlapped))
168     {
169         // The asynchronous read request could be satisfied immediately.
170         return ERROR_SUCCESS;
171     }
172 
173     DWORD dwLastError = GetLastError();
174     if (dwLastError == ERROR_IO_PENDING)
175     {
176         // The asynchronous read request could not be satisfied immediately, so wait for it with the given timeout.
177         DWORD dwWaitResult = WaitForSingleObject(m_ReadOverlapped.hEvent, TimeoutMilliseconds);
178         if (dwWaitResult == WAIT_OBJECT_0)
179         {
180             // Fill NumberOfBytesRead.
181             if (GetOverlappedResult(m_hReadPipe, &m_ReadOverlapped, NumberOfBytesRead, FALSE))
182             {
183                 // We successfully read NumberOfBytesRead bytes.
184                 return ERROR_SUCCESS;
185             }
186 
187             dwLastError = GetLastError();
188             if (dwLastError == ERROR_BROKEN_PIPE)
189             {
190                 // The other end of the pipe has been closed.
191                 return ERROR_BROKEN_PIPE;
192             }
193             else
194             {
195                 // An unexpected error.
196                 FATAL("GetOverlappedResult failed\n");
197             }
198         }
199         else
200         {
201             // This may be WAIT_TIMEOUT or an unexpected error.
202             return dwWaitResult;
203         }
204     }
205     else
206     {
207         // This may be ERROR_BROKEN_PIPE or an unexpected error.
208         return dwLastError;
209     }
210 }
211 
212 /**
213  * Writes data to the write pipe.
214  *
215  * This function must not be called after CloseWritePipe.
216  *
217  * @param Buffer
218  * Buffer containing the data to write.
219  *
220  * @param NumberOfBytesToWrite
221  * The number of bytes to write to the pipe from Buffer.
222  *
223  * @param NumberOfBytesWritten
224  * On return, the number of bytes actually written to the pipe.
225  *
226  * @return
227  * True on success, false on failure; call GetLastError for error information.
228  *
229  * @see WriteFile
230  */
231 bool
Write(LPCVOID Buffer,DWORD NumberOfBytesToWrite,PDWORD NumberOfBytesWritten)232 CPipe::Write(LPCVOID Buffer, DWORD NumberOfBytesToWrite, PDWORD NumberOfBytesWritten)
233 {
234     if (!m_hWritePipe)
235         FATAL("Trying to write to a closed write pipe\n");
236 
237     return WriteFile(m_hWritePipe, Buffer, NumberOfBytesToWrite, NumberOfBytesWritten, NULL);
238 }
239