FreeRDP
serial_main.c
1 
22 #include <freerdp/config.h>
23 
24 #include <errno.h>
25 #include <stdio.h>
26 #include <stdint.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include <winpr/collections.h>
31 #include <winpr/comm.h>
32 #include <winpr/crt.h>
33 #include <winpr/stream.h>
34 #include <winpr/synch.h>
35 #include <winpr/thread.h>
36 #include <winpr/wlog.h>
37 #include <winpr/assert.h>
38 
39 #include <freerdp/freerdp.h>
40 #include <freerdp/channels/rdpdr.h>
41 #include <freerdp/channels/log.h>
42 #include <freerdp/utils/rdpdr_utils.h>
43 
44 #define TAG CHANNELS_TAG("serial.client")
45 
46 #define MAX_IRP_THREADS 5
47 
48 typedef struct
49 {
50  DEVICE device;
51  BOOL permissive;
52  SERIAL_DRIVER_ID ServerSerialDriverId;
53  HANDLE* hComm;
54 
55  wLog* log;
56  HANDLE MainThread;
57  wMessageQueue* MainIrpQueue;
58 
59  /* one thread per pending IRP and indexed according their CompletionId */
60  wListDictionary* IrpThreads;
61  CRITICAL_SECTION TerminatingIrpThreadsLock;
62  rdpContext* rdpcontext;
63 } SERIAL_DEVICE;
64 
65 typedef struct
66 {
67  SERIAL_DEVICE* serial;
68  IRP* irp;
69 } IRP_THREAD_DATA;
70 
71 static void close_terminated_irp_thread_handles(SERIAL_DEVICE* serial, BOOL forceClose);
72 static UINT32 GetLastErrorToIoStatus(SERIAL_DEVICE* serial)
73 {
74  /* http://msdn.microsoft.com/en-us/library/ff547466%28v=vs.85%29.aspx#generic_status_values_for_serial_device_control_requests
75  */
76  switch (GetLastError())
77  {
78  case ERROR_BAD_DEVICE:
79  return STATUS_INVALID_DEVICE_REQUEST;
80 
81  case ERROR_CALL_NOT_IMPLEMENTED:
82  return STATUS_NOT_IMPLEMENTED;
83 
84  case ERROR_CANCELLED:
85  return STATUS_CANCELLED;
86 
87  case ERROR_INSUFFICIENT_BUFFER:
88  return STATUS_BUFFER_TOO_SMALL; /* NB: STATUS_BUFFER_SIZE_TOO_SMALL not defined */
89 
90  case ERROR_INVALID_DEVICE_OBJECT_PARAMETER: /* eg: SerCx2.sys' _purge() */
91  return STATUS_INVALID_DEVICE_STATE;
92 
93  case ERROR_INVALID_HANDLE:
94  return STATUS_INVALID_DEVICE_REQUEST;
95 
96  case ERROR_INVALID_PARAMETER:
97  return STATUS_INVALID_PARAMETER;
98 
99  case ERROR_IO_DEVICE:
100  return STATUS_IO_DEVICE_ERROR;
101 
102  case ERROR_IO_PENDING:
103  return STATUS_PENDING;
104 
105  case ERROR_NOT_SUPPORTED:
106  return STATUS_NOT_SUPPORTED;
107 
108  case ERROR_TIMEOUT:
109  return STATUS_TIMEOUT;
110  default:
111  break;
112  }
113 
114  WLog_Print(serial->log, WLOG_DEBUG, "unexpected last-error: 0x%08" PRIX32 "", GetLastError());
115  return STATUS_UNSUCCESSFUL;
116 }
117 
118 static UINT serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp)
119 {
120  DWORD DesiredAccess = 0;
121  DWORD SharedAccess = 0;
122  DWORD CreateDisposition = 0;
123  UINT32 PathLength = 0;
124 
125  WINPR_ASSERT(serial);
126  WINPR_ASSERT(irp);
127 
128  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32))
129  return ERROR_INVALID_DATA;
130 
131  Stream_Read_UINT32(irp->input, DesiredAccess); /* DesiredAccess (4 bytes) */
132  Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */
133  Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */
134  Stream_Read_UINT32(irp->input, SharedAccess); /* SharedAccess (4 bytes) */
135  Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */
136  Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */
137  Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */
138 
139  if (!Stream_SafeSeek(irp->input, PathLength)) /* Path (variable) */
140  return ERROR_INVALID_DATA;
141 
142  WINPR_ASSERT(PathLength == 0); /* MS-RDPESP 2.2.2.2 */
143 #ifndef _WIN32
144  /* Windows 2012 server sends on a first call :
145  * DesiredAccess = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES
146  * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ
147  * CreateDisposition = 0x00000001: CREATE_NEW
148  *
149  * then Windows 2012 sends :
150  * DesiredAccess = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |
151  * FILE_READ_EA | FILE_READ_DATA SharedAccess = 0x00000007: FILE_SHARE_DELETE |
152  * FILE_SHARE_WRITE | FILE_SHARE_READ CreateDisposition = 0x00000001: CREATE_NEW
153  *
154  * WINPR_ASSERT(DesiredAccess == (GENERIC_READ | GENERIC_WRITE));
155  * WINPR_ASSERT(SharedAccess == 0);
156  * WINPR_ASSERT(CreateDisposition == OPEN_EXISTING);
157  *
158  */
159  WLog_Print(serial->log, WLOG_DEBUG,
160  "DesiredAccess: 0x%" PRIX32 ", SharedAccess: 0x%" PRIX32
161  ", CreateDisposition: 0x%" PRIX32 "",
162  DesiredAccess, SharedAccess, CreateDisposition);
163  /* FIXME: As of today only the flags below are supported by CommCreateFileA: */
164  DesiredAccess = GENERIC_READ | GENERIC_WRITE;
165  SharedAccess = 0;
166  CreateDisposition = OPEN_EXISTING;
167 #endif
168  serial->hComm =
169  CreateFile(serial->device.name, DesiredAccess, SharedAccess, NULL, /* SecurityAttributes */
170  CreateDisposition, 0, /* FlagsAndAttributes */
171  NULL); /* TemplateFile */
172 
173  if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE))
174  {
175  WLog_Print(serial->log, WLOG_WARN, "CreateFile failure: %s last-error: 0x%08" PRIX32 "",
176  serial->device.name, GetLastError());
177  irp->IoStatus = STATUS_UNSUCCESSFUL;
178  goto error_handle;
179  }
180 
181  _comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId);
182  _comm_set_permissive(serial->hComm, serial->permissive);
183  /* NOTE: binary mode/raw mode required for the redirection. On
184  * Linux, CommCreateFileA forces this setting.
185  */
186  /* ZeroMemory(&dcb, sizeof(DCB)); */
187  /* dcb.DCBlength = sizeof(DCB); */
188  /* GetCommState(serial->hComm, &dcb); */
189  /* dcb.fBinary = TRUE; */
190  /* SetCommState(serial->hComm, &dcb); */
191  WINPR_ASSERT(irp->FileId == 0);
192  irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */
193  irp->IoStatus = STATUS_SUCCESS;
194  WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") created.",
195  serial->device.name, irp->device->id, irp->FileId);
196 error_handle:
197  Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */
198  Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */
199  return CHANNEL_RC_OK;
200 }
201 
202 static UINT serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp)
203 {
204  WINPR_ASSERT(serial);
205  WINPR_ASSERT(irp);
206 
207  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32))
208  return ERROR_INVALID_DATA;
209 
210  Stream_Seek(irp->input, 32); /* Padding (32 bytes) */
211 
212  close_terminated_irp_thread_handles(serial, TRUE);
213 
214  if (!CloseHandle(serial->hComm))
215  {
216  WLog_Print(serial->log, WLOG_WARN, "CloseHandle failure: %s (%" PRIu32 ") closed.",
217  serial->device.name, irp->device->id);
218  irp->IoStatus = STATUS_UNSUCCESSFUL;
219  goto error_handle;
220  }
221 
222  WLog_Print(serial->log, WLOG_DEBUG, "%s (DeviceId: %" PRIu32 ", FileId: %" PRIu32 ") closed.",
223  serial->device.name, irp->device->id, irp->FileId);
224  irp->IoStatus = STATUS_SUCCESS;
225 error_handle:
226  serial->hComm = NULL;
227  Stream_Zero(irp->output, 5); /* Padding (5 bytes) */
228  return CHANNEL_RC_OK;
229 }
230 
236 static UINT serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp)
237 {
238  UINT32 Length = 0;
239  UINT64 Offset = 0;
240  BYTE* buffer = NULL;
241  DWORD nbRead = 0;
242 
243  WINPR_ASSERT(serial);
244  WINPR_ASSERT(irp);
245 
246  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32))
247  return ERROR_INVALID_DATA;
248 
249  Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
250  Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
251  (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message
252  * ignored */
253  Stream_Seek(irp->input, 20); /* Padding (20 bytes) */
254  buffer = (BYTE*)calloc(Length, sizeof(BYTE));
255 
256  if (buffer == NULL)
257  {
258  irp->IoStatus = STATUS_NO_MEMORY;
259  goto error_handle;
260  }
261 
262  /* MS-RDPESP 3.2.5.1.4: If the Offset field is not set to 0, the value MUST be ignored
263  * WINPR_ASSERT(Offset == 0);
264  */
265  WLog_Print(serial->log, WLOG_DEBUG, "reading %" PRIu32 " bytes from %s", Length,
266  serial->device.name);
267 
268  /* FIXME: CommReadFile to be replaced by ReadFile */
269  if (CommReadFile(serial->hComm, buffer, Length, &nbRead, NULL))
270  {
271  irp->IoStatus = STATUS_SUCCESS;
272  }
273  else
274  {
275  WLog_Print(serial->log, WLOG_DEBUG,
276  "read failure to %s, nbRead=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
277  serial->device.name, nbRead, GetLastError());
278  irp->IoStatus = GetLastErrorToIoStatus(serial);
279  }
280 
281  WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes read from %s", nbRead,
282  serial->device.name);
283 error_handle:
284  Stream_Write_UINT32(irp->output, nbRead); /* Length (4 bytes) */
285 
286  if (nbRead > 0)
287  {
288  if (!Stream_EnsureRemainingCapacity(irp->output, nbRead))
289  {
290  WLog_Print(serial->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
291  free(buffer);
292  return CHANNEL_RC_NO_MEMORY;
293  }
294 
295  Stream_Write(irp->output, buffer, nbRead); /* ReadData */
296  }
297 
298  free(buffer);
299  return CHANNEL_RC_OK;
300 }
301 
302 static UINT serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp)
303 {
304  UINT32 Length = 0;
305  UINT64 Offset = 0;
306  DWORD nbWritten = 0;
307 
308  WINPR_ASSERT(serial);
309  WINPR_ASSERT(irp);
310 
311  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32))
312  return ERROR_INVALID_DATA;
313 
314  Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */
315  Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */
316  (void)Offset; /* [MS-RDPESP] 3.2.5.1.4 Processing a Server Read Request Message
317  * ignored */
318  if (!Stream_SafeSeek(irp->input, 20)) /* Padding (20 bytes) */
319  return ERROR_INVALID_DATA;
320 
321  /* MS-RDPESP 3.2.5.1.5: The Offset field is ignored
322  * WINPR_ASSERT(Offset == 0);
323  *
324  * Using a serial printer, noticed though this field could be
325  * set.
326  */
327  WLog_Print(serial->log, WLOG_DEBUG, "writing %" PRIu32 " bytes to %s", Length,
328  serial->device.name);
329 
330  const void* ptr = Stream_ConstPointer(irp->input);
331  if (!Stream_SafeSeek(irp->input, Length))
332  return ERROR_INVALID_DATA;
333  /* FIXME: CommWriteFile to be replaced by WriteFile */
334  if (CommWriteFile(serial->hComm, ptr, Length, &nbWritten, NULL))
335  {
336  irp->IoStatus = STATUS_SUCCESS;
337  }
338  else
339  {
340  WLog_Print(serial->log, WLOG_DEBUG,
341  "write failure to %s, nbWritten=%" PRIu32 ", last-error: 0x%08" PRIX32 "",
342  serial->device.name, nbWritten, GetLastError());
343  irp->IoStatus = GetLastErrorToIoStatus(serial);
344  }
345 
346  WLog_Print(serial->log, WLOG_DEBUG, "%" PRIu32 " bytes written to %s", nbWritten,
347  serial->device.name);
348  Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */
349  Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */
350  return CHANNEL_RC_OK;
351 }
352 
358 static UINT serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp)
359 {
360  UINT32 IoControlCode = 0;
361  UINT32 InputBufferLength = 0;
362  BYTE* InputBuffer = NULL;
363  UINT32 OutputBufferLength = 0;
364  BYTE* OutputBuffer = NULL;
365  DWORD BytesReturned = 0;
366 
367  WINPR_ASSERT(serial);
368  WINPR_ASSERT(irp);
369 
370  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, 32))
371  return ERROR_INVALID_DATA;
372 
373  Stream_Read_UINT32(irp->input, OutputBufferLength); /* OutputBufferLength (4 bytes) */
374  Stream_Read_UINT32(irp->input, InputBufferLength); /* InputBufferLength (4 bytes) */
375  Stream_Read_UINT32(irp->input, IoControlCode); /* IoControlCode (4 bytes) */
376  Stream_Seek(irp->input, 20); /* Padding (20 bytes) */
377 
378  if (!Stream_CheckAndLogRequiredLengthWLog(serial->log, irp->input, InputBufferLength))
379  return ERROR_INVALID_DATA;
380 
381  OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE));
382 
383  if (OutputBuffer == NULL)
384  {
385  irp->IoStatus = STATUS_NO_MEMORY;
386  goto error_handle;
387  }
388 
389  InputBuffer = (BYTE*)calloc(InputBufferLength, sizeof(BYTE));
390 
391  if (InputBuffer == NULL)
392  {
393  irp->IoStatus = STATUS_NO_MEMORY;
394  goto error_handle;
395  }
396 
397  Stream_Read(irp->input, InputBuffer, InputBufferLength);
398  WLog_Print(serial->log, WLOG_DEBUG,
399  "CommDeviceIoControl: CompletionId=%" PRIu32 ", IoControlCode=[0x%" PRIX32 "] %s",
400  irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode));
401 
402  /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */
403  if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, InputBufferLength,
404  OutputBuffer, OutputBufferLength, &BytesReturned, NULL))
405  {
406  /* WLog_Print(serial->log, WLOG_DEBUG, "CommDeviceIoControl: CompletionId=%"PRIu32",
407  * IoControlCode=[0x%"PRIX32"] %s done", irp->CompletionId, IoControlCode,
408  * _comm_serial_ioctl_name(IoControlCode)); */
409  irp->IoStatus = STATUS_SUCCESS;
410  }
411  else
412  {
413  WLog_Print(serial->log, WLOG_DEBUG,
414  "CommDeviceIoControl failure: IoControlCode=[0x%" PRIX32
415  "] %s, last-error: 0x%08" PRIX32 "",
416  IoControlCode, _comm_serial_ioctl_name(IoControlCode), GetLastError());
417  irp->IoStatus = GetLastErrorToIoStatus(serial);
418  }
419 
420 error_handle:
421  /* FIXME: find out whether it's required or not to get
422  * BytesReturned == OutputBufferLength when
423  * CommDeviceIoControl returns FALSE */
424  WINPR_ASSERT(OutputBufferLength == BytesReturned);
425  Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */
426 
427  if (BytesReturned > 0)
428  {
429  if (!Stream_EnsureRemainingCapacity(irp->output, BytesReturned))
430  {
431  WLog_Print(serial->log, WLOG_ERROR, "Stream_EnsureRemainingCapacity failed!");
432  free(InputBuffer);
433  free(OutputBuffer);
434  return CHANNEL_RC_NO_MEMORY;
435  }
436 
437  Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */
438  }
439 
440  /* FIXME: Why at least Windows 2008R2 gets lost with this
441  * extra byte and likely on a IOCTL_SERIAL_SET_BAUD_RATE? The
442  * extra byte is well required according MS-RDPEFS
443  * 2.2.1.5.5 */
444  /* else */
445  /* { */
446  /* Stream_Write_UINT8(irp->output, 0); /\* Padding (1 byte) *\/ */
447  /* } */
448  free(InputBuffer);
449  free(OutputBuffer);
450  return CHANNEL_RC_OK;
451 }
452 
458 static UINT serial_process_irp(SERIAL_DEVICE* serial, IRP* irp)
459 {
460  UINT error = CHANNEL_RC_OK;
461 
462  WINPR_ASSERT(serial);
463  WINPR_ASSERT(irp);
464 
465  WLog_Print(serial->log, WLOG_DEBUG, "IRP MajorFunction: %s, MinorFunction: 0x%08" PRIX32 "\n",
466  rdpdr_irp_string(irp->MajorFunction), irp->MinorFunction);
467 
468  switch (irp->MajorFunction)
469  {
470  case IRP_MJ_CREATE:
471  error = serial_process_irp_create(serial, irp);
472  break;
473 
474  case IRP_MJ_CLOSE:
475  error = serial_process_irp_close(serial, irp);
476  break;
477 
478  case IRP_MJ_READ:
479  error = serial_process_irp_read(serial, irp);
480  break;
481 
482  case IRP_MJ_WRITE:
483  error = serial_process_irp_write(serial, irp);
484  break;
485 
486  case IRP_MJ_DEVICE_CONTROL:
487  error = serial_process_irp_device_control(serial, irp);
488  break;
489 
490  default:
491  irp->IoStatus = STATUS_NOT_SUPPORTED;
492  break;
493  }
494 
495  DWORD level = WLOG_TRACE;
496  if (error)
497  level = WLOG_WARN;
498 
499  WLog_Print(serial->log, level,
500  "[%s|0x%08" PRIx32 "] completed with %s [0x%08" PRIx32 "] (IoStatus %s [0x%08" PRIx32
501  "])",
502  rdpdr_irp_string(irp->MajorFunction), irp->MajorFunction, WTSErrorToString(error),
503  error, NtStatus2Tag(irp->IoStatus), irp->IoStatus);
504 
505  return error;
506 }
507 
508 static DWORD WINAPI irp_thread_func(LPVOID arg)
509 {
510  IRP_THREAD_DATA* data = (IRP_THREAD_DATA*)arg;
511  UINT error = 0;
512 
513  WINPR_ASSERT(data);
514  WINPR_ASSERT(data->serial);
515  WINPR_ASSERT(data->irp);
516 
517  /* blocks until the end of the request */
518  if ((error = serial_process_irp(data->serial, data->irp)))
519  {
520  WLog_Print(data->serial->log, WLOG_ERROR,
521  "serial_process_irp failed with error %" PRIu32 "", error);
522  goto error_out;
523  }
524 
525  EnterCriticalSection(&data->serial->TerminatingIrpThreadsLock);
526  error = data->irp->Complete(data->irp);
527  LeaveCriticalSection(&data->serial->TerminatingIrpThreadsLock);
528 error_out:
529 
530  if (error && data->serial->rdpcontext)
531  setChannelError(data->serial->rdpcontext, error, "irp_thread_func reported an error");
532 
533  /* NB: At this point, the server might already being reusing
534  * the CompletionId whereas the thread is not yet
535  * terminated */
536  free(data);
537  ExitThread(error);
538  return error;
539 }
540 
541 static void close_unterminated_irp_thread(wListDictionary* list, wLog* log, ULONG_PTR id)
542 {
543  WINPR_ASSERT(list);
544  HANDLE self = _GetCurrentThread();
545  HANDLE cirpThread = ListDictionary_GetItemValue(list, (void*)id);
546  if (self == cirpThread)
547  WLog_Print(log, WLOG_DEBUG, "Skipping termination of own IRP thread");
548  else
549  ListDictionary_Remove(list, (void*)id);
550 }
551 
552 static void close_terminated_irp_thread(wListDictionary* list, wLog* log, ULONG_PTR id)
553 {
554  WINPR_ASSERT(list);
555 
556  HANDLE cirpThread = ListDictionary_GetItemValue(list, (void*)id);
557  /* FIXME: not quite sure a zero timeout is a good thing to check whether a thread is
558  * stil alived or not */
559  const DWORD waitResult = WaitForSingleObject(cirpThread, 0);
560 
561  if (waitResult == WAIT_OBJECT_0)
562  ListDictionary_Remove(list, (void*)id);
563  else if (waitResult != WAIT_TIMEOUT)
564  {
565  /* unexpected thread state */
566  WLog_Print(log, WLOG_WARN, "WaitForSingleObject, got an unexpected result=0x%" PRIX32 "\n",
567  waitResult);
568  }
569 }
570 
571 void close_terminated_irp_thread_handles(SERIAL_DEVICE* serial, BOOL forceClose)
572 {
573  WINPR_ASSERT(serial);
574 
575  EnterCriticalSection(&serial->TerminatingIrpThreadsLock);
576 
577  ULONG_PTR* ids = NULL;
578  const size_t nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids);
579 
580  for (size_t i = 0; i < nbIds; i++)
581  {
582  ULONG_PTR id = ids[i];
583  if (forceClose)
584  close_unterminated_irp_thread(serial->IrpThreads, serial->log, id);
585  else
586  close_terminated_irp_thread(serial->IrpThreads, serial->log, id);
587  }
588 
589  free(ids);
590 
591  LeaveCriticalSection(&serial->TerminatingIrpThreadsLock);
592 }
593 
594 static void create_irp_thread(SERIAL_DEVICE* serial, IRP* irp)
595 {
596  IRP_THREAD_DATA* data = NULL;
597  HANDLE irpThread = NULL;
598  HANDLE previousIrpThread = NULL;
599  uintptr_t key = 0;
600 
601  WINPR_ASSERT(serial);
602  WINPR_ASSERT(irp);
603 
604  close_terminated_irp_thread_handles(serial, FALSE);
605 
606  /* NB: At this point and thanks to the synchronization we're
607  * sure that the incoming IRP uses well a recycled
608  * CompletionId or the server sent again an IRP already posted
609  * which didn't get yet a response (this later server behavior
610  * at least observed with IOCTL_SERIAL_WAIT_ON_MASK and
611  * mstsc.exe).
612  *
613  * FIXME: behavior documented somewhere? behavior not yet
614  * observed with FreeRDP).
615  */
616  key = irp->CompletionId + 1ull;
617  previousIrpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)key);
618 
619  if (previousIrpThread)
620  {
621  /* Thread still alived <=> Request still pending */
622  WLog_Print(serial->log, WLOG_DEBUG,
623  "IRP recall: IRP with the CompletionId=%" PRIu32 " not yet completed!",
624  irp->CompletionId);
625  WINPR_ASSERT(FALSE); /* unimplemented */
626  /* TODO: WINPR_ASSERTs that previousIrpThread handles well
627  * the same request by checking more details. Need an
628  * access to the IRP object used by previousIrpThread
629  */
630  /* TODO: taking over the pending IRP or sending a kind
631  * of wake up signal to accelerate the pending
632  * request
633  *
634  * To be considered:
635  * if (IoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) {
636  * pComm->PendingEvents |= SERIAL_EV_FREERDP_*;
637  * }
638  */
639  irp->Discard(irp);
640  return;
641  }
642 
643  if (ListDictionary_Count(serial->IrpThreads) >= MAX_IRP_THREADS)
644  {
645  WLog_Print(serial->log, WLOG_WARN,
646  "Number of IRP threads threshold reached: %" PRIuz ", keep on anyway",
647  ListDictionary_Count(serial->IrpThreads));
648  WINPR_ASSERT(FALSE); /* unimplemented */
649  /* TODO: MAX_IRP_THREADS has been thought to avoid a
650  * flooding of pending requests. Use
651  * WaitForMultipleObjects() when available in winpr
652  * for threads.
653  */
654  }
655 
656  /* error_handle to be used ... */
657  data = (IRP_THREAD_DATA*)calloc(1, sizeof(IRP_THREAD_DATA));
658 
659  if (data == NULL)
660  {
661  WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP_THREAD_DATA.");
662  goto error_handle;
663  }
664 
665  data->serial = serial;
666  data->irp = irp;
667  /* data freed by irp_thread_func */
668  irpThread = CreateThread(NULL, 0, irp_thread_func, (void*)data, CREATE_SUSPENDED, NULL);
669 
670  if (irpThread == INVALID_HANDLE_VALUE)
671  {
672  WLog_Print(serial->log, WLOG_WARN, "Could not allocate a new IRP thread.");
673  goto error_handle;
674  }
675 
676  key = irp->CompletionId + 1ull;
677 
678  if (!ListDictionary_Add(serial->IrpThreads, (void*)key, irpThread))
679  {
680  WLog_Print(serial->log, WLOG_ERROR, "ListDictionary_Add failed!");
681  goto error_handle;
682  }
683 
684  ResumeThread(irpThread);
685 
686  return;
687 error_handle:
688  if (irpThread)
689  (void)CloseHandle(irpThread);
690  irp->IoStatus = STATUS_NO_MEMORY;
691  irp->Complete(irp);
692  free(data);
693 }
694 
695 static DWORD WINAPI serial_thread_func(LPVOID arg)
696 {
697  IRP* irp = NULL;
698  wMessage message = { 0 };
699  SERIAL_DEVICE* serial = (SERIAL_DEVICE*)arg;
700  UINT error = CHANNEL_RC_OK;
701 
702  WINPR_ASSERT(serial);
703 
704  while (1)
705  {
706  if (!MessageQueue_Wait(serial->MainIrpQueue))
707  {
708  WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Wait failed!");
709  error = ERROR_INTERNAL_ERROR;
710  break;
711  }
712 
713  if (!MessageQueue_Peek(serial->MainIrpQueue, &message, TRUE))
714  {
715  WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Peek failed!");
716  error = ERROR_INTERNAL_ERROR;
717  break;
718  }
719 
720  if (message.id == WMQ_QUIT)
721  break;
722 
723  irp = (IRP*)message.wParam;
724 
725  if (irp)
726  create_irp_thread(serial, irp);
727  }
728 
729  ListDictionary_Clear(serial->IrpThreads);
730  if (error && serial->rdpcontext)
731  setChannelError(serial->rdpcontext, error, "serial_thread_func reported an error");
732 
733  ExitThread(error);
734  return error;
735 }
736 
742 static UINT serial_irp_request(DEVICE* device, IRP* irp)
743 {
744  SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device;
745  WINPR_ASSERT(irp != NULL);
746  WINPR_ASSERT(serial);
747 
748  if (irp == NULL)
749  return CHANNEL_RC_OK;
750 
751  /* NB: ENABLE_ASYNCIO is set, (MS-RDPEFS 2.2.2.7.2) this
752  * allows the server to send multiple simultaneous read or
753  * write requests.
754  */
755 
756  if (!MessageQueue_Post(serial->MainIrpQueue, NULL, 0, (void*)irp, NULL))
757  {
758  WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_Post failed!");
759  return ERROR_INTERNAL_ERROR;
760  }
761 
762  return CHANNEL_RC_OK;
763 }
764 
770 static UINT serial_free(DEVICE* device)
771 {
772  UINT error = 0;
773  SERIAL_DEVICE* serial = (SERIAL_DEVICE*)device;
774  if (!serial)
775  return CHANNEL_RC_OK;
776 
777  WLog_Print(serial->log, WLOG_DEBUG, "freeing");
778  if (serial->MainIrpQueue)
779  MessageQueue_PostQuit(serial->MainIrpQueue, 0);
780 
781  if (serial->MainThread)
782  {
783  if (WaitForSingleObject(serial->MainThread, INFINITE) == WAIT_FAILED)
784  {
785  error = GetLastError();
786  WLog_Print(serial->log, WLOG_ERROR,
787  "WaitForSingleObject failed with error %" PRIu32 "!", error);
788  }
789  (void)CloseHandle(serial->MainThread);
790  }
791 
792  if (serial->hComm)
793  (void)CloseHandle(*serial->hComm);
794 
795  /* Clean up resources */
796  Stream_Free(serial->device.data, TRUE);
797  MessageQueue_Free(serial->MainIrpQueue);
798  ListDictionary_Free(serial->IrpThreads);
799  DeleteCriticalSection(&serial->TerminatingIrpThreadsLock);
800  free(serial);
801  return CHANNEL_RC_OK;
802 }
803 
804 static void serial_message_free(void* obj)
805 {
806  wMessage* msg = obj;
807  if (!msg)
808  return;
809  if (msg->id != 0)
810  return;
811 
812  IRP* irp = (IRP*)msg->wParam;
813  if (!irp)
814  return;
815  WINPR_ASSERT(irp->Discard);
816  irp->Discard(irp);
817 }
818 
819 static void irp_thread_close(void* arg)
820 {
821  HANDLE hdl = arg;
822  if (hdl)
823  {
824  HANDLE thz = _GetCurrentThread();
825  if (thz == hdl)
826  WLog_WARN(TAG, "closing self, ignoring...");
827  else
828  {
829  (void)TerminateThread(hdl, 0);
830  (void)WaitForSingleObject(hdl, INFINITE);
831  (void)CloseHandle(hdl);
832  }
833  }
834 }
835 
841 FREERDP_ENTRY_POINT(
842  UINT VCAPITYPE serial_DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints))
843 {
844  size_t len = 0;
845  SERIAL_DEVICE* serial = NULL;
846  UINT error = CHANNEL_RC_OK;
847 
848  WINPR_ASSERT(pEntryPoints);
849 
850  RDPDR_SERIAL* device = (RDPDR_SERIAL*)pEntryPoints->device;
851  WINPR_ASSERT(device);
852 
853  wLog* log = WLog_Get(TAG);
854  const char* name = device->device.Name;
855  const char* path = device->Path;
856  const char* driver = device->Driver;
857 
858  if (!name || (name[0] == '*'))
859  {
860  /* TODO: implement auto detection of serial ports */
861  WLog_Print(log, WLOG_WARN,
862  "Serial port autodetection not implemented, nothing will be redirected!");
863  return CHANNEL_RC_OK;
864  }
865 
866  if ((name && name[0]) && (path && path[0]))
867  {
868  WLog_Print(log, WLOG_DEBUG, "Defining %s as %s", name, path);
869 
870  if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */))
871  {
872  DWORD status = GetLastError();
873  WLog_Print(log, WLOG_ERROR, "DefineCommDevice failed with %08" PRIx32, status);
874  return ERROR_INTERNAL_ERROR;
875  }
876 
877  serial = (SERIAL_DEVICE*)calloc(1, sizeof(SERIAL_DEVICE));
878 
879  if (!serial)
880  {
881  WLog_Print(log, WLOG_ERROR, "calloc failed!");
882  return CHANNEL_RC_NO_MEMORY;
883  }
884 
885  serial->log = log;
886  serial->device.type = RDPDR_DTYP_SERIAL;
887  serial->device.name = name;
888  serial->device.IRPRequest = serial_irp_request;
889  serial->device.Free = serial_free;
890  serial->rdpcontext = pEntryPoints->rdpcontext;
891  len = strlen(name);
892  serial->device.data = Stream_New(NULL, len + 1);
893 
894  if (!serial->device.data)
895  {
896  WLog_Print(serial->log, WLOG_ERROR, "calloc failed!");
897  error = CHANNEL_RC_NO_MEMORY;
898  goto error_out;
899  }
900 
901  for (size_t i = 0; i <= len; i++)
902  Stream_Write_UINT8(serial->device.data, name[i] < 0 ? '_' : name[i]);
903 
904  if (driver != NULL)
905  {
906  if (_stricmp(driver, "Serial") == 0)
907  serial->ServerSerialDriverId = SerialDriverSerialSys;
908  else if (_stricmp(driver, "SerCx") == 0)
909  serial->ServerSerialDriverId = SerialDriverSerCxSys;
910  else if (_stricmp(driver, "SerCx2") == 0)
911  serial->ServerSerialDriverId = SerialDriverSerCx2Sys;
912  else
913  {
914  WLog_Print(serial->log, WLOG_WARN, "Unknown server's serial driver: %s.", driver);
915  WLog_Print(serial->log, WLOG_WARN,
916  "Valid options are: 'Serial' (default), 'SerCx' and 'SerCx2'");
917  goto error_out;
918  }
919  }
920  else
921  {
922  /* default driver */
923  serial->ServerSerialDriverId = SerialDriverSerialSys;
924  }
925 
926  if (device->Permissive != NULL)
927  {
928  if (_stricmp(device->Permissive, "permissive") == 0)
929  {
930  serial->permissive = TRUE;
931  }
932  else
933  {
934  WLog_Print(serial->log, WLOG_WARN, "Unknown flag: %s", device->Permissive);
935  goto error_out;
936  }
937  }
938 
939  WLog_Print(serial->log, WLOG_DEBUG, "Server's serial driver: %s (id: %d)", driver,
940  serial->ServerSerialDriverId);
941 
942  serial->MainIrpQueue = MessageQueue_New(NULL);
943 
944  if (!serial->MainIrpQueue)
945  {
946  WLog_Print(serial->log, WLOG_ERROR, "MessageQueue_New failed!");
947  error = CHANNEL_RC_NO_MEMORY;
948  goto error_out;
949  }
950 
951  {
952  wObject* obj = MessageQueue_Object(serial->MainIrpQueue);
953  WINPR_ASSERT(obj);
954  obj->fnObjectFree = serial_message_free;
955  }
956 
957  /* IrpThreads content only modified by create_irp_thread() */
958  serial->IrpThreads = ListDictionary_New(FALSE);
959 
960  if (!serial->IrpThreads)
961  {
962  WLog_Print(serial->log, WLOG_ERROR, "ListDictionary_New failed!");
963  error = CHANNEL_RC_NO_MEMORY;
964  goto error_out;
965  }
966 
967  {
968  wObject* obj = ListDictionary_ValueObject(serial->IrpThreads);
969  WINPR_ASSERT(obj);
970  obj->fnObjectFree = irp_thread_close;
971  }
972 
973  InitializeCriticalSection(&serial->TerminatingIrpThreadsLock);
974 
975  error = pEntryPoints->RegisterDevice(pEntryPoints->devman, &serial->device);
976  if (error != CHANNEL_RC_OK)
977  {
978  WLog_Print(serial->log, WLOG_ERROR,
979  "EntryPoints->RegisterDevice failed with error %" PRIu32 "!", error);
980  goto error_out;
981  }
982 
983  serial->MainThread = CreateThread(NULL, 0, serial_thread_func, serial, 0, NULL);
984  if (!serial->MainThread)
985  {
986  WLog_Print(serial->log, WLOG_ERROR, "CreateThread failed!");
987  error = ERROR_INTERNAL_ERROR;
988  goto error_out;
989  }
990  }
991 
992  return error;
993 error_out:
994  if (serial)
995  serial_free(&serial->device);
996  return error;
997 }
This struct contains function pointer to initialize/free objects.
Definition: collections.h:57