Coverage Report

Created: 2024-05-20 06:11

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