Coverage Report

Created: 2024-05-20 06:11

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