Coverage Report

Created: 2026-03-04 06:13

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/FreeRDP/winpr/libwinpr/comm/comm_io.c
Line
Count
Source
1
/**
2
 * WinPR: Windows Portable Runtime
3
 * Serial Communication API
4
 *
5
 * Copyright 2014 Hewlett-Packard Development Company, L.P.
6
 *
7
 * Licensed under the Apache License, Version 2.0 (the "License");
8
 * you may not use this file except in compliance with the License.
9
 * You may obtain a copy of the License at
10
 *
11
 *     http://www.apache.org/licenses/LICENSE-2.0
12
 *
13
 * Unless required by applicable law or agreed to in writing, software
14
 * distributed under the License is distributed on an "AS IS" BASIS,
15
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
 * See the License for the specific language governing permissions and
17
 * limitations under the License.
18
 */
19
20
#include <winpr/config.h>
21
22
#include <winpr/assert.h>
23
#include <errno.h>
24
#include <termios.h>
25
#include <unistd.h>
26
27
#include <winpr/io.h>
28
#include <winpr/wlog.h>
29
#include <winpr/wtypes.h>
30
31
#include "comm.h"
32
33
BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive)
34
0
{
35
0
  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
36
37
0
  if (!CommIsHandled(hDevice))
38
0
    return FALSE;
39
40
0
  pComm->permissive = permissive;
41
0
  return TRUE;
42
0
}
43
44
/* Computes VTIME in deciseconds from Ti in milliseconds */
45
static UCHAR svtime(ULONG Ti)
46
0
{
47
  /* FIXME: look for an equivalent math function otherwise let
48
   * do the compiler do the optimization */
49
0
  if (Ti == 0)
50
0
    return 0;
51
0
  else if (Ti < 100)
52
0
    return 1;
53
0
  else if (Ti > 25500)
54
0
    return 255; /* 0xFF */
55
0
  else
56
0
    return (UCHAR)(Ti / 100);
57
0
}
58
59
/**
60
 * ERRORS:
61
 *   ERROR_INVALID_HANDLE
62
 *   ERROR_NOT_SUPPORTED
63
 *   ERROR_INVALID_PARAMETER
64
 *   ERROR_TIMEOUT
65
 *   ERROR_IO_DEVICE
66
 *   ERROR_BAD_DEVICE
67
 */
68
BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
69
                  LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
70
0
{
71
0
  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
72
0
  int biggestFd = -1;
73
0
  fd_set read_set;
74
0
  int nbFds = 0;
75
0
  COMMTIMEOUTS* pTimeouts = nullptr;
76
0
  UCHAR vmin = 0;
77
0
  UCHAR vtime = 0;
78
0
  LONGLONG Tmax = 0;
79
0
  struct timeval tmaxTimeout;
80
0
  struct timeval* pTmaxTimeout = nullptr;
81
0
  struct termios currentTermios;
82
0
  EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */
83
84
0
  if (!CommIsHandled(hDevice))
85
0
    goto return_false;
86
87
0
  if (lpOverlapped != nullptr)
88
0
  {
89
0
    SetLastError(ERROR_NOT_SUPPORTED);
90
0
    goto return_false;
91
0
  }
92
93
0
  if (lpNumberOfBytesRead == nullptr)
94
0
  {
95
0
    SetLastError(
96
0
        ERROR_INVALID_PARAMETER); /* since we doesn't support lpOverlapped != nullptr */
97
0
    goto return_false;
98
0
  }
99
100
0
  *lpNumberOfBytesRead = 0; /* will be adjusted if required ... */
101
102
0
  if (nNumberOfBytesToRead <= 0) /* N */
103
0
  {
104
0
    goto return_true; /* FIXME: or FALSE? */
105
0
  }
106
107
0
  if (tcgetattr(pComm->fd, &currentTermios) < 0)
108
0
  {
109
0
    SetLastError(ERROR_IO_DEVICE);
110
0
    goto return_false;
111
0
  }
112
113
0
  if (currentTermios.c_lflag & ICANON)
114
0
  {
115
0
    CommLog_Print(WLOG_WARN, "Canonical mode not supported"); /* the timeout could not be set */
116
0
    SetLastError(ERROR_NOT_SUPPORTED);
117
0
    goto return_false;
118
0
  }
119
120
  /* http://msdn.microsoft.com/en-us/library/hh439614%28v=vs.85%29.aspx
121
   * http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx
122
   *
123
   * ReadIntervalTimeout  | ReadTotalTimeoutMultiplier | ReadTotalTimeoutConstant | VMIN | VTIME |
124
   * TMAX  | 0            |            0               |           0              |   N  |   0   |
125
   * INDEF | Blocks for N bytes available. 0< Ti <MAXULONG  |            0               | 0 |
126
   * N  |   Ti  | INDEF | Blocks on first byte, then use Ti between bytes. MAXULONG       | 0 | 0
127
   * |   0  |   0   |   0   | Returns immediately with bytes available (don't block) MAXULONG |
128
   * MAXULONG           |      0< Tc <MAXULONG     |   N  |   0   |   Tc  | Blocks on first byte
129
   * during Tc or returns immediately with bytes available MAXULONG       |            m |
130
   * MAXULONG          |                      | Invalid 0            |            m |      0< Tc
131
   * <MAXULONG     |   N  |   0   |  Tmax | Blocks on first byte during Tmax or returns
132
   * immediately with bytes available 0< Ti <MAXULONG    |            m               |      0<
133
   * Tc <MAXULONG     |   N  |   Ti  |  Tmax | Blocks on first byte, then use Ti between bytes.
134
   * Tmax is used for the whole system call.
135
   */
136
  /* NB: timeouts are in milliseconds, VTIME are in deciseconds and is an unsigned char */
137
  /* FIXME: double check whether open(pComm->fd_read_event, O_NONBLOCK) doesn't conflict with
138
   * above use cases */
139
0
  pTimeouts = &(pComm->timeouts);
140
141
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
142
0
      (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
143
0
  {
144
0
    CommLog_Print(
145
0
        WLOG_WARN,
146
0
        "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
147
0
    SetLastError(ERROR_INVALID_PARAMETER);
148
0
    goto return_false;
149
0
  }
150
151
  /* VMIN */
152
153
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
154
0
      (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0))
155
0
  {
156
0
    vmin = 0;
157
0
  }
158
0
  else
159
0
  {
160
    /* N */
161
    /* vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255;*/ /* 0xFF */
162
    /* NB: we might wait endlessly with vmin=N, prefer to
163
     * force vmin=1 and return with bytes
164
     * available. FIXME: is a feature disarded here? */
165
0
    vmin = 1;
166
0
  }
167
168
  /* VTIME */
169
170
0
  if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG))
171
0
  {
172
    /* Ti */
173
0
    vtime = svtime(pTimeouts->ReadIntervalTimeout);
174
0
  }
175
176
  /* TMAX */
177
0
  pTmaxTimeout = &tmaxTimeout;
178
179
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
180
0
      (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG))
181
0
  {
182
    /* Tc */
183
0
    Tmax = pTimeouts->ReadTotalTimeoutConstant;
184
0
  }
185
0
  else
186
0
  {
187
    /* Tmax */
188
0
    Tmax = 1ll * nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier +
189
0
           1ll * pTimeouts->ReadTotalTimeoutConstant;
190
191
    /* INDEFinitely */
192
0
    if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) &&
193
0
        (pTimeouts->ReadTotalTimeoutMultiplier == 0))
194
0
      pTmaxTimeout = nullptr;
195
0
  }
196
197
0
  if ((currentTermios.c_cc[VMIN] != vmin) || (currentTermios.c_cc[VTIME] != vtime))
198
0
  {
199
0
    currentTermios.c_cc[VMIN] = vmin;
200
0
    currentTermios.c_cc[VTIME] = vtime;
201
202
0
    if (tcsetattr(pComm->fd, TCSANOW, &currentTermios) < 0)
203
0
    {
204
0
      CommLog_Print(WLOG_WARN,
205
0
                    "CommReadFile failure, could not apply new timeout values: VMIN=%" PRIu8
206
0
                    ", VTIME=%" PRIu8 "",
207
0
                    vmin, vtime);
208
0
      SetLastError(ERROR_IO_DEVICE);
209
0
      goto return_false;
210
0
    }
211
0
  }
212
213
  /* wait indefinitely if pTmaxTimeout is nullptr */
214
215
0
  if (pTmaxTimeout != nullptr)
216
0
  {
217
0
    ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
218
219
0
    if (Tmax > 0) /* return immdiately if Tmax == 0 */
220
0
    {
221
0
      pTmaxTimeout->tv_sec = Tmax / 1000;           /* s */
222
0
      pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
223
0
    }
224
0
  }
225
226
  /* FIXME: had expected eventfd_write() to return EAGAIN when
227
   * there is no eventfd_read() but this not the case. */
228
  /* discard a possible and no more relevant event */
229
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
230
0
  {
231
0
    eventfd_t val = 0;
232
0
    (void)eventfd_read(pComm->fd_read_event, &val);
233
0
  }
234
0
#endif
235
0
  biggestFd = pComm->fd_read;
236
237
0
  if (pComm->fd_read_event > biggestFd)
238
0
    biggestFd = pComm->fd_read_event;
239
240
0
  FD_ZERO(&read_set);
241
0
  WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE);
242
0
  WINPR_ASSERT(pComm->fd_read < FD_SETSIZE);
243
0
  FD_SET(pComm->fd_read_event, &read_set);
244
0
  FD_SET(pComm->fd_read, &read_set);
245
0
  nbFds = select(biggestFd + 1, &read_set, nullptr, nullptr, pTmaxTimeout);
246
247
0
  if (nbFds < 0)
248
0
  {
249
0
    char ebuffer[256] = WINPR_C_ARRAY_INIT;
250
0
    CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
251
0
                  winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
252
0
    SetLastError(ERROR_IO_DEVICE);
253
0
    goto return_false;
254
0
  }
255
256
0
  if (nbFds == 0)
257
0
  {
258
    /* timeout */
259
0
    SetLastError(ERROR_TIMEOUT);
260
0
    goto return_false;
261
0
  }
262
263
  /* read_set */
264
265
0
  if (FD_ISSET(pComm->fd_read_event, &read_set))
266
0
  {
267
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
268
0
    eventfd_t event = 0;
269
270
0
    if (eventfd_read(pComm->fd_read_event, &event) < 0)
271
0
    {
272
0
      if (errno == EAGAIN)
273
0
      {
274
0
        WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
275
                             /* keep on */
276
0
      }
277
0
      else
278
0
      {
279
0
        char ebuffer[256] = WINPR_C_ARRAY_INIT;
280
0
        CommLog_Print(WLOG_WARN,
281
0
                      "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno,
282
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
283
        /* FIXME: goto return_false ? */
284
0
      }
285
286
0
      WINPR_ASSERT(errno == EAGAIN);
287
0
    }
288
289
0
    if (event == WINPR_PURGE_RXABORT)
290
0
    {
291
0
      SetLastError(ERROR_CANCELLED);
292
0
      goto return_false;
293
0
    }
294
295
0
    WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */
296
0
#endif
297
0
  }
298
299
0
  if (FD_ISSET(pComm->fd_read, &read_set))
300
0
  {
301
0
    ssize_t nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead);
302
303
0
    if ((nbRead < 0) || (nbRead > nNumberOfBytesToRead))
304
0
    {
305
0
      char ebuffer[256] = WINPR_C_ARRAY_INIT;
306
0
      CommLog_Print(WLOG_WARN,
307
0
                    "CommReadFile failed, ReadIntervalTimeout=%" PRIu32
308
0
                    ", ReadTotalTimeoutMultiplier=%" PRIu32
309
0
                    ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u",
310
0
                    pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier,
311
0
                    pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN],
312
0
                    currentTermios.c_cc[VTIME]);
313
0
      CommLog_Print(
314
0
          WLOG_WARN, "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s",
315
0
          nNumberOfBytesToRead, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
316
317
0
      if (errno == EAGAIN)
318
0
      {
319
        /* keep on */
320
0
        goto return_true; /* expect a read-loop to be implemented on the server side */
321
0
      }
322
0
      else if (errno == EBADF)
323
0
      {
324
0
        SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
325
0
        goto return_false;
326
0
      }
327
0
      else
328
0
      {
329
0
        WINPR_ASSERT(FALSE);
330
0
        SetLastError(ERROR_IO_DEVICE);
331
0
        goto return_false;
332
0
      }
333
0
    }
334
335
0
    if (nbRead == 0)
336
0
    {
337
      /* termios timeout */
338
0
      SetLastError(ERROR_TIMEOUT);
339
0
      goto return_false;
340
0
    }
341
342
0
    *lpNumberOfBytesRead = WINPR_ASSERTING_INT_CAST(UINT32, nbRead);
343
344
0
    EnterCriticalSection(&pComm->EventsLock);
345
0
    if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
346
0
    {
347
0
      if (pComm->eventChar != '\0' &&
348
0
          memchr(lpBuffer, pComm->eventChar, WINPR_ASSERTING_INT_CAST(size_t, nbRead)))
349
0
        pComm->PendingEvents |= SERIAL_EV_RXCHAR;
350
0
    }
351
0
    LeaveCriticalSection(&pComm->EventsLock);
352
0
    goto return_true;
353
0
  }
354
355
0
  WINPR_ASSERT(FALSE);
356
0
  *lpNumberOfBytesRead = 0;
357
0
return_false:
358
0
  LeaveCriticalSection(&pComm->ReadLock);
359
0
  return FALSE;
360
0
return_true:
361
0
  LeaveCriticalSection(&pComm->ReadLock);
362
0
  return TRUE;
363
0
}
364
365
/**
366
 * ERRORS:
367
 *   ERROR_INVALID_HANDLE
368
 *   ERROR_NOT_SUPPORTED
369
 *   ERROR_INVALID_PARAMETER
370
 *   ERROR_BAD_DEVICE
371
 */
372
BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
373
                   LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
374
0
{
375
0
  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
376
0
  struct timeval tmaxTimeout;
377
0
  struct timeval* pTmaxTimeout = nullptr;
378
0
  EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */
379
380
0
  if (!CommIsHandled(hDevice))
381
0
    goto return_false;
382
383
0
  if (lpOverlapped != nullptr)
384
0
  {
385
0
    SetLastError(ERROR_NOT_SUPPORTED);
386
0
    goto return_false;
387
0
  }
388
389
0
  if (lpNumberOfBytesWritten == nullptr)
390
0
  {
391
0
    SetLastError(
392
0
        ERROR_INVALID_PARAMETER); /* since we doesn't support lpOverlapped != nullptr */
393
0
    goto return_false;
394
0
  }
395
396
0
  *lpNumberOfBytesWritten = 0; /* will be adjusted if required ... */
397
398
0
  if (nNumberOfBytesToWrite <= 0)
399
0
  {
400
0
    goto return_true; /* FIXME: or FALSE? */
401
0
  }
402
403
  /* FIXME: had expected eventfd_write() to return EAGAIN when
404
   * there is no eventfd_read() but this not the case. */
405
  /* discard a possible and no more relevant event */
406
407
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
408
0
  {
409
0
    eventfd_t val = 0;
410
0
    (void)eventfd_read(pComm->fd_write_event, &val);
411
0
  }
412
0
#endif
413
414
0
  {
415
    /* ms */
416
0
    const LONGLONG Tmax =
417
0
        1ll * nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier +
418
0
        1ll * pComm->timeouts.WriteTotalTimeoutConstant;
419
    /* NB: select() may update the timeout argument to indicate
420
     * how much time was left. Keep the timeout variable out of
421
     * the while() */
422
0
    pTmaxTimeout = &tmaxTimeout;
423
0
    ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
424
425
0
    if (Tmax > 0)
426
0
    {
427
0
      pTmaxTimeout->tv_sec = Tmax / 1000;           /* s */
428
0
      pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
429
0
    }
430
0
    else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) &&
431
0
             (pComm->timeouts.WriteTotalTimeoutConstant == 0))
432
0
    {
433
0
      pTmaxTimeout = nullptr;
434
0
    }
435
0
  }
436
437
  /* else return immdiately */
438
439
0
  while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite)
440
0
  {
441
0
    int biggestFd = -1;
442
0
    fd_set event_set;
443
0
    fd_set write_set;
444
0
    int nbFds = 0;
445
0
    biggestFd = pComm->fd_write;
446
447
0
    if (pComm->fd_write_event > biggestFd)
448
0
      biggestFd = pComm->fd_write_event;
449
450
0
    FD_ZERO(&event_set);
451
0
    FD_ZERO(&write_set);
452
0
    WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE);
453
0
    WINPR_ASSERT(pComm->fd_write < FD_SETSIZE);
454
0
    FD_SET(pComm->fd_write_event, &event_set);
455
0
    FD_SET(pComm->fd_write, &write_set);
456
0
    nbFds = select(biggestFd + 1, &event_set, &write_set, nullptr, pTmaxTimeout);
457
458
0
    if (nbFds < 0)
459
0
    {
460
0
      char ebuffer[256] = WINPR_C_ARRAY_INIT;
461
0
      CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
462
0
                    winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
463
0
      SetLastError(ERROR_IO_DEVICE);
464
0
      goto return_false;
465
0
    }
466
467
0
    if (nbFds == 0)
468
0
    {
469
      /* timeout */
470
0
      SetLastError(ERROR_TIMEOUT);
471
0
      goto return_false;
472
0
    }
473
474
    /* event_set */
475
476
0
    if (FD_ISSET(pComm->fd_write_event, &event_set))
477
0
    {
478
0
#if defined(WINPR_HAVE_SYS_EVENTFD_H)
479
0
      eventfd_t event = 0;
480
481
0
      if (eventfd_read(pComm->fd_write_event, &event) < 0)
482
0
      {
483
0
        if (errno == EAGAIN)
484
0
        {
485
0
          WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
486
                               /* keep on */
487
0
        }
488
0
        else
489
0
        {
490
0
          char ebuffer[256] = WINPR_C_ARRAY_INIT;
491
0
          CommLog_Print(WLOG_WARN,
492
0
                        "unexpected error on reading fd_write_event, errno=[%d] %s\n",
493
0
                        errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
494
          /* FIXME: goto return_false ? */
495
0
        }
496
497
0
        WINPR_ASSERT(errno == EAGAIN);
498
0
      }
499
500
0
      if (event == WINPR_PURGE_TXABORT)
501
0
      {
502
0
        SetLastError(ERROR_CANCELLED);
503
0
        goto return_false;
504
0
      }
505
506
0
      WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */
507
0
#endif
508
0
    }
509
510
    /* write_set */
511
512
0
    if (FD_ISSET(pComm->fd_write, &write_set))
513
0
    {
514
0
      ssize_t nbWritten = 0;
515
0
      const BYTE* ptr = lpBuffer;
516
0
      nbWritten = write(pComm->fd_write, &ptr[*lpNumberOfBytesWritten],
517
0
                        nNumberOfBytesToWrite - (*lpNumberOfBytesWritten));
518
519
0
      if (nbWritten < 0)
520
0
      {
521
0
        char ebuffer[256] = WINPR_C_ARRAY_INIT;
522
0
        CommLog_Print(WLOG_WARN,
523
0
                      "CommWriteFile failed after %" PRIu32
524
0
                      " bytes written, errno=[%d] %s\n",
525
0
                      *lpNumberOfBytesWritten, errno,
526
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
527
528
0
        if (errno == EAGAIN)
529
0
        {
530
          /* keep on */
531
0
          continue;
532
0
        }
533
0
        else if (errno == EBADF)
534
0
        {
535
0
          SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
536
0
          goto return_false;
537
0
        }
538
0
        else
539
0
        {
540
0
          WINPR_ASSERT(FALSE);
541
0
          SetLastError(ERROR_IO_DEVICE);
542
0
          goto return_false;
543
0
        }
544
0
      }
545
546
0
      *lpNumberOfBytesWritten += nbWritten;
547
0
    }
548
0
  } /* while */
549
550
  /* FIXME: this call to tcdrain() doesn't look correct and
551
   * might hide a bug but was required while testing a serial
552
   * printer. Its driver was expecting the modem line status
553
   * SERIAL_MSR_DSR true after the sending which was never
554
   * happening otherwise. A purge was also done before each
555
   * Write operation. The serial port was opened with:
556
   * DesiredAccess=0x0012019F. The printer worked fine with
557
   * mstsc. */
558
0
  tcdrain(pComm->fd_write);
559
560
0
return_true:
561
0
  LeaveCriticalSection(&pComm->WriteLock);
562
0
  return TRUE;
563
564
0
return_false:
565
0
  LeaveCriticalSection(&pComm->WriteLock);
566
0
  return FALSE;
567
0
}