Coverage Report

Created: 2024-09-08 06:16

/src/FreeRDP/winpr/libwinpr/comm/comm_io.c
Line
Count
Source (jump to first uncovered line)
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 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 = NULL;
76
0
  UCHAR vmin = 0;
77
0
  UCHAR vtime = 0;
78
0
  ULONGLONG Tmax = 0;
79
0
  struct timeval tmaxTimeout;
80
0
  struct timeval* pTmaxTimeout = NULL;
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 != NULL)
88
0
  {
89
0
    SetLastError(ERROR_NOT_SUPPORTED);
90
0
    goto return_false;
91
0
  }
92
93
0
  if (lpNumberOfBytesRead == NULL)
94
0
  {
95
0
    SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
96
0
    goto return_false;
97
0
  }
98
99
0
  *lpNumberOfBytesRead = 0; /* will be ajusted if required ... */
100
101
0
  if (nNumberOfBytesToRead <= 0) /* N */
102
0
  {
103
0
    goto return_true; /* FIXME: or FALSE? */
104
0
  }
105
106
0
  if (tcgetattr(pComm->fd, &currentTermios) < 0)
107
0
  {
108
0
    SetLastError(ERROR_IO_DEVICE);
109
0
    goto return_false;
110
0
  }
111
112
0
  if (currentTermios.c_lflag & ICANON)
113
0
  {
114
0
    CommLog_Print(WLOG_WARN, "Canonical mode not supported"); /* the timeout could not be set */
115
0
    SetLastError(ERROR_NOT_SUPPORTED);
116
0
    goto return_false;
117
0
  }
118
119
  /* http://msdn.microsoft.com/en-us/library/hh439614%28v=vs.85%29.aspx
120
   * http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx
121
   *
122
   * ReadIntervalTimeout  | ReadTotalTimeoutMultiplier | ReadTotalTimeoutConstant | VMIN | VTIME |
123
   * TMAX  | 0            |            0               |           0              |   N  |   0   |
124
   * INDEF | Blocks for N bytes available. 0< Ti <MAXULONG  |            0               | 0 |
125
   * N  |   Ti  | INDEF | Blocks on first byte, then use Ti between bytes. MAXULONG       | 0 | 0
126
   * |   0  |   0   |   0   | Returns immediately with bytes available (don't block) MAXULONG |
127
   * MAXULONG           |      0< Tc <MAXULONG     |   N  |   0   |   Tc  | Blocks on first byte
128
   * during Tc or returns immediately whith bytes available MAXULONG       |            m |
129
   * MAXULONG          |                      | Invalid 0            |            m |      0< Tc
130
   * <MAXULONG     |   N  |   0   |  Tmax | Blocks on first byte during Tmax or returns
131
   * immediately whith bytes available 0< Ti <MAXULONG    |            m               |      0<
132
   * Tc <MAXULONG     |   N  |   Ti  |  Tmax | Blocks on first byte, then use Ti between bytes.
133
   * Tmax is used for the whole system call.
134
   */
135
  /* NB: timeouts are in milliseconds, VTIME are in deciseconds and is an unsigned char */
136
  /* FIXME: double check whether open(pComm->fd_read_event, O_NONBLOCK) doesn't conflict with
137
   * above use cases */
138
0
  pTimeouts = &(pComm->timeouts);
139
140
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
141
0
      (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
142
0
  {
143
0
    CommLog_Print(
144
0
        WLOG_WARN,
145
0
        "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
146
0
    SetLastError(ERROR_INVALID_PARAMETER);
147
0
    goto return_false;
148
0
  }
149
150
  /* VMIN */
151
152
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
153
0
      (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0))
154
0
  {
155
0
    vmin = 0;
156
0
  }
157
0
  else
158
0
  {
159
    /* N */
160
    /* vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255;*/ /* 0xFF */
161
    /* NB: we might wait endlessly with vmin=N, prefer to
162
     * force vmin=1 and return with bytes
163
     * available. FIXME: is a feature disarded here? */
164
0
    vmin = 1;
165
0
  }
166
167
  /* VTIME */
168
169
0
  if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG))
170
0
  {
171
    /* Ti */
172
0
    vtime = svtime(pTimeouts->ReadIntervalTimeout);
173
0
  }
174
175
  /* TMAX */
176
0
  pTmaxTimeout = &tmaxTimeout;
177
178
0
  if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
179
0
      (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG))
180
0
  {
181
    /* Tc */
182
0
    Tmax = pTimeouts->ReadTotalTimeoutConstant;
183
0
  }
184
0
  else
185
0
  {
186
    /* Tmax */
187
0
    Tmax = 1ull * nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier +
188
0
           1ull * pTimeouts->ReadTotalTimeoutConstant;
189
190
    /* INDEFinitely */
191
0
    if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) &&
192
0
        (pTimeouts->ReadTotalTimeoutMultiplier == 0))
193
0
      pTmaxTimeout = NULL;
194
0
  }
195
196
0
  if ((currentTermios.c_cc[VMIN] != vmin) || (currentTermios.c_cc[VTIME] != vtime))
197
0
  {
198
0
    currentTermios.c_cc[VMIN] = vmin;
199
0
    currentTermios.c_cc[VTIME] = vtime;
200
201
0
    if (tcsetattr(pComm->fd, TCSANOW, &currentTermios) < 0)
202
0
    {
203
0
      CommLog_Print(WLOG_WARN,
204
0
                    "CommReadFile failure, could not apply new timeout values: VMIN=%" PRIu8
205
0
                    ", VTIME=%" PRIu8 "",
206
0
                    vmin, vtime);
207
0
      SetLastError(ERROR_IO_DEVICE);
208
0
      goto return_false;
209
0
    }
210
0
  }
211
212
  /* wait indefinitely if pTmaxTimeout is NULL */
213
214
0
  if (pTmaxTimeout != NULL)
215
0
  {
216
0
    ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
217
218
0
    if (Tmax > 0) /* return immdiately if Tmax == 0 */
219
0
    {
220
0
      pTmaxTimeout->tv_sec = Tmax / 1000;           /* s */
221
0
      pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
222
0
    }
223
0
  }
224
225
  /* FIXME: had expected eventfd_write() to return EAGAIN when
226
   * there is no eventfd_read() but this not the case. */
227
  /* discard a possible and no more relevant event */
228
0
  eventfd_read(pComm->fd_read_event, NULL);
229
0
  biggestFd = pComm->fd_read;
230
231
0
  if (pComm->fd_read_event > biggestFd)
232
0
    biggestFd = pComm->fd_read_event;
233
234
0
  FD_ZERO(&read_set);
235
0
  WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE);
236
0
  WINPR_ASSERT(pComm->fd_read < FD_SETSIZE);
237
0
  FD_SET(pComm->fd_read_event, &read_set);
238
0
  FD_SET(pComm->fd_read, &read_set);
239
0
  nbFds = select(biggestFd + 1, &read_set, NULL, NULL, pTmaxTimeout);
240
241
0
  if (nbFds < 0)
242
0
  {
243
0
    char ebuffer[256] = { 0 };
244
0
    CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
245
0
                  winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
246
0
    SetLastError(ERROR_IO_DEVICE);
247
0
    goto return_false;
248
0
  }
249
250
0
  if (nbFds == 0)
251
0
  {
252
    /* timeout */
253
0
    SetLastError(ERROR_TIMEOUT);
254
0
    goto return_false;
255
0
  }
256
257
  /* read_set */
258
259
0
  if (FD_ISSET(pComm->fd_read_event, &read_set))
260
0
  {
261
0
    eventfd_t event = 0;
262
263
0
    if (eventfd_read(pComm->fd_read_event, &event) < 0)
264
0
    {
265
0
      if (errno == EAGAIN)
266
0
      {
267
0
        WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
268
                             /* keep on */
269
0
      }
270
0
      else
271
0
      {
272
0
        char ebuffer[256] = { 0 };
273
0
        CommLog_Print(WLOG_WARN,
274
0
                      "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno,
275
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
276
        /* FIXME: goto return_false ? */
277
0
      }
278
279
0
      WINPR_ASSERT(errno == EAGAIN);
280
0
    }
281
282
0
    if (event == WINPR_PURGE_RXABORT)
283
0
    {
284
0
      SetLastError(ERROR_CANCELLED);
285
0
      goto return_false;
286
0
    }
287
288
0
    WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */
289
0
  }
290
291
0
  if (FD_ISSET(pComm->fd_read, &read_set))
292
0
  {
293
0
    ssize_t nbRead = 0;
294
0
    nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead);
295
296
0
    if (nbRead < 0)
297
0
    {
298
0
      char ebuffer[256] = { 0 };
299
0
      CommLog_Print(WLOG_WARN,
300
0
                    "CommReadFile failed, ReadIntervalTimeout=%" PRIu32
301
0
                    ", ReadTotalTimeoutMultiplier=%" PRIu32
302
0
                    ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u",
303
0
                    pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier,
304
0
                    pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN],
305
0
                    currentTermios.c_cc[VTIME]);
306
0
      CommLog_Print(
307
0
          WLOG_WARN, "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s",
308
0
          nNumberOfBytesToRead, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
309
310
0
      if (errno == EAGAIN)
311
0
      {
312
        /* keep on */
313
0
        goto return_true; /* expect a read-loop to be implemented on the server side */
314
0
      }
315
0
      else if (errno == EBADF)
316
0
      {
317
0
        SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
318
0
        goto return_false;
319
0
      }
320
0
      else
321
0
      {
322
0
        WINPR_ASSERT(FALSE);
323
0
        SetLastError(ERROR_IO_DEVICE);
324
0
        goto return_false;
325
0
      }
326
0
    }
327
328
0
    if (nbRead == 0)
329
0
    {
330
      /* termios timeout */
331
0
      SetLastError(ERROR_TIMEOUT);
332
0
      goto return_false;
333
0
    }
334
335
0
    *lpNumberOfBytesRead = nbRead;
336
337
0
    EnterCriticalSection(&pComm->EventsLock);
338
0
    if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
339
0
    {
340
0
      if (pComm->eventChar != '\0' && memchr(lpBuffer, pComm->eventChar, nbRead))
341
0
        pComm->PendingEvents |= SERIAL_EV_RXCHAR;
342
0
    }
343
0
    LeaveCriticalSection(&pComm->EventsLock);
344
0
    goto return_true;
345
0
  }
346
347
0
  WINPR_ASSERT(FALSE);
348
0
  *lpNumberOfBytesRead = 0;
349
0
return_false:
350
0
  LeaveCriticalSection(&pComm->ReadLock);
351
0
  return FALSE;
352
0
return_true:
353
0
  LeaveCriticalSection(&pComm->ReadLock);
354
0
  return TRUE;
355
0
}
356
357
/**
358
 * ERRORS:
359
 *   ERROR_INVALID_HANDLE
360
 *   ERROR_NOT_SUPPORTED
361
 *   ERROR_INVALID_PARAMETER
362
 *   ERROR_BAD_DEVICE
363
 */
364
BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
365
                   LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
366
0
{
367
0
  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
368
0
  struct timeval tmaxTimeout;
369
0
  struct timeval* pTmaxTimeout = NULL;
370
0
  EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */
371
372
0
  if (!CommIsHandled(hDevice))
373
0
    goto return_false;
374
375
0
  if (lpOverlapped != NULL)
376
0
  {
377
0
    SetLastError(ERROR_NOT_SUPPORTED);
378
0
    goto return_false;
379
0
  }
380
381
0
  if (lpNumberOfBytesWritten == NULL)
382
0
  {
383
0
    SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
384
0
    goto return_false;
385
0
  }
386
387
0
  *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */
388
389
0
  if (nNumberOfBytesToWrite <= 0)
390
0
  {
391
0
    goto return_true; /* FIXME: or FALSE? */
392
0
  }
393
394
  /* FIXME: had expected eventfd_write() to return EAGAIN when
395
   * there is no eventfd_read() but this not the case. */
396
  /* discard a possible and no more relevant event */
397
0
  eventfd_read(pComm->fd_write_event, NULL);
398
  /* ms */
399
0
  ULONGLONG Tmax = 1ull * nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier +
400
0
                   1ull * pComm->timeouts.WriteTotalTimeoutConstant;
401
  /* NB: select() may update the timeout argument to indicate
402
   * how much time was left. Keep the timeout variable out of
403
   * the while() */
404
0
  pTmaxTimeout = &tmaxTimeout;
405
0
  ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
406
407
0
  if (Tmax > 0)
408
0
  {
409
0
    pTmaxTimeout->tv_sec = Tmax / 1000;           /* s */
410
0
    pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
411
0
  }
412
0
  else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) &&
413
0
           (pComm->timeouts.WriteTotalTimeoutConstant == 0))
414
0
  {
415
0
    pTmaxTimeout = NULL;
416
0
  }
417
418
  /* else return immdiately */
419
420
0
  while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite)
421
0
  {
422
0
    int biggestFd = -1;
423
0
    fd_set event_set;
424
0
    fd_set write_set;
425
0
    int nbFds = 0;
426
0
    biggestFd = pComm->fd_write;
427
428
0
    if (pComm->fd_write_event > biggestFd)
429
0
      biggestFd = pComm->fd_write_event;
430
431
0
    FD_ZERO(&event_set);
432
0
    FD_ZERO(&write_set);
433
0
    WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE);
434
0
    WINPR_ASSERT(pComm->fd_write < FD_SETSIZE);
435
0
    FD_SET(pComm->fd_write_event, &event_set);
436
0
    FD_SET(pComm->fd_write, &write_set);
437
0
    nbFds = select(biggestFd + 1, &event_set, &write_set, NULL, pTmaxTimeout);
438
439
0
    if (nbFds < 0)
440
0
    {
441
0
      char ebuffer[256] = { 0 };
442
0
      CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
443
0
                    winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
444
0
      SetLastError(ERROR_IO_DEVICE);
445
0
      goto return_false;
446
0
    }
447
448
0
    if (nbFds == 0)
449
0
    {
450
      /* timeout */
451
0
      SetLastError(ERROR_TIMEOUT);
452
0
      goto return_false;
453
0
    }
454
455
    /* event_set */
456
457
0
    if (FD_ISSET(pComm->fd_write_event, &event_set))
458
0
    {
459
0
      eventfd_t event = 0;
460
461
0
      if (eventfd_read(pComm->fd_write_event, &event) < 0)
462
0
      {
463
0
        if (errno == EAGAIN)
464
0
        {
465
0
          WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
466
                               /* keep on */
467
0
        }
468
0
        else
469
0
        {
470
0
          char ebuffer[256] = { 0 };
471
0
          CommLog_Print(WLOG_WARN,
472
0
                        "unexpected error on reading fd_write_event, errno=[%d] %s\n",
473
0
                        errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
474
          /* FIXME: goto return_false ? */
475
0
        }
476
477
0
        WINPR_ASSERT(errno == EAGAIN);
478
0
      }
479
480
0
      if (event == WINPR_PURGE_TXABORT)
481
0
      {
482
0
        SetLastError(ERROR_CANCELLED);
483
0
        goto return_false;
484
0
      }
485
486
0
      WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */
487
0
    }
488
489
    /* write_set */
490
491
0
    if (FD_ISSET(pComm->fd_write, &write_set))
492
0
    {
493
0
      ssize_t nbWritten = 0;
494
0
      nbWritten = write(pComm->fd_write, ((const BYTE*)lpBuffer) + (*lpNumberOfBytesWritten),
495
0
                        nNumberOfBytesToWrite - (*lpNumberOfBytesWritten));
496
497
0
      if (nbWritten < 0)
498
0
      {
499
0
        char ebuffer[256] = { 0 };
500
0
        CommLog_Print(WLOG_WARN,
501
0
                      "CommWriteFile failed after %" PRIu32
502
0
                      " bytes written, errno=[%d] %s\n",
503
0
                      *lpNumberOfBytesWritten, errno,
504
0
                      winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
505
506
0
        if (errno == EAGAIN)
507
0
        {
508
          /* keep on */
509
0
          continue;
510
0
        }
511
0
        else if (errno == EBADF)
512
0
        {
513
0
          SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
514
0
          goto return_false;
515
0
        }
516
0
        else
517
0
        {
518
0
          WINPR_ASSERT(FALSE);
519
0
          SetLastError(ERROR_IO_DEVICE);
520
0
          goto return_false;
521
0
        }
522
0
      }
523
524
0
      *lpNumberOfBytesWritten += nbWritten;
525
0
    }
526
0
  } /* while */
527
528
  /* FIXME: this call to tcdrain() doesn't look correct and
529
   * might hide a bug but was required while testing a serial
530
   * printer. Its driver was expecting the modem line status
531
   * SERIAL_MSR_DSR true after the sending which was never
532
   * happenning otherwise. A purge was also done before each
533
   * Write operation. The serial port was opened with:
534
   * DesiredAccess=0x0012019F. The printer worked fine with
535
   * mstsc. */
536
0
  tcdrain(pComm->fd_write);
537
538
0
return_true:
539
0
  LeaveCriticalSection(&pComm->WriteLock);
540
0
  return TRUE;
541
542
0
return_false:
543
0
  LeaveCriticalSection(&pComm->WriteLock);
544
0
  return FALSE;
545
0
}