Coverage Report

Created: 2023-11-19 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
#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;
77
0
  COMMTIMEOUTS* pTimeouts;
78
0
  UCHAR vmin = 0;
79
0
  UCHAR vtime = 0;
80
0
  ULONGLONG Tmax = 0;
81
0
  struct timeval tmaxTimeout, *pTmaxTimeout;
82
0
  struct termios currentTermios;
83
0
  EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */
84
85
0
  if (!CommIsHandled(hDevice))
86
0
    goto return_false;
87
88
0
  if (lpOverlapped != NULL)
89
0
  {
90
0
    SetLastError(ERROR_NOT_SUPPORTED);
91
0
    goto return_false;
92
0
  }
93
94
0
  if (lpNumberOfBytesRead == NULL)
95
0
  {
96
0
    SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
97
0
    goto return_false;
98
0
  }
99
100
0
  *lpNumberOfBytesRead = 0; /* will be ajusted 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 whith 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 whith 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 = _vtime(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 = nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier +
189
0
           pTimeouts->ReadTotalTimeoutConstant;
190
191
    /* INDEFinitely */
192
0
    if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) &&
193
0
        (pTimeouts->ReadTotalTimeoutMultiplier == 0))
194
0
      pTmaxTimeout = NULL;
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 NULL */
214
215
0
  if (pTmaxTimeout != NULL)
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
  eventfd_read(pComm->fd_read_event, NULL);
230
0
  biggestFd = pComm->fd_read;
231
232
0
  if (pComm->fd_read_event > biggestFd)
233
0
    biggestFd = pComm->fd_read_event;
234
235
0
  FD_ZERO(&read_set);
236
0
  WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE);
237
0
  WINPR_ASSERT(pComm->fd_read < FD_SETSIZE);
238
0
  FD_SET(pComm->fd_read_event, &read_set);
239
0
  FD_SET(pComm->fd_read, &read_set);
240
0
  nbFds = select(biggestFd + 1, &read_set, NULL, NULL, pTmaxTimeout);
241
242
0
  if (nbFds < 0)
243
0
  {
244
0
    CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno, strerror(errno));
245
0
    SetLastError(ERROR_IO_DEVICE);
246
0
    goto return_false;
247
0
  }
248
249
0
  if (nbFds == 0)
250
0
  {
251
    /* timeout */
252
0
    SetLastError(ERROR_TIMEOUT);
253
0
    goto return_false;
254
0
  }
255
256
  /* read_set */
257
258
0
  if (FD_ISSET(pComm->fd_read_event, &read_set))
259
0
  {
260
0
    eventfd_t event = 0;
261
262
0
    if (eventfd_read(pComm->fd_read_event, &event) < 0)
263
0
    {
264
0
      if (errno == EAGAIN)
265
0
      {
266
0
        WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
267
                             /* keep on */
268
0
      }
269
0
      else
270
0
      {
271
0
        CommLog_Print(WLOG_WARN,
272
0
                      "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno,
273
0
                      strerror(errno));
274
        /* FIXME: goto return_false ? */
275
0
      }
276
277
0
      WINPR_ASSERT(errno == EAGAIN);
278
0
    }
279
280
0
    if (event == WINPR_PURGE_RXABORT)
281
0
    {
282
0
      SetLastError(ERROR_CANCELLED);
283
0
      goto return_false;
284
0
    }
285
286
0
    WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */
287
0
  }
288
289
0
  if (FD_ISSET(pComm->fd_read, &read_set))
290
0
  {
291
0
    ssize_t nbRead = 0;
292
0
    nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead);
293
294
0
    if (nbRead < 0)
295
0
    {
296
0
      CommLog_Print(WLOG_WARN,
297
0
                    "CommReadFile failed, ReadIntervalTimeout=%" PRIu32
298
0
                    ", ReadTotalTimeoutMultiplier=%" PRIu32
299
0
                    ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u",
300
0
                    pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier,
301
0
                    pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN],
302
0
                    currentTermios.c_cc[VTIME]);
303
0
      CommLog_Print(WLOG_WARN,
304
0
                    "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s",
305
0
                    nNumberOfBytesToRead, errno, strerror(errno));
306
307
0
      if (errno == EAGAIN)
308
0
      {
309
        /* keep on */
310
0
        goto return_true; /* expect a read-loop to be implemented on the server side */
311
0
      }
312
0
      else if (errno == EBADF)
313
0
      {
314
0
        SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
315
0
        goto return_false;
316
0
      }
317
0
      else
318
0
      {
319
0
        WINPR_ASSERT(FALSE);
320
0
        SetLastError(ERROR_IO_DEVICE);
321
0
        goto return_false;
322
0
      }
323
0
    }
324
325
0
    if (nbRead == 0)
326
0
    {
327
      /* termios timeout */
328
0
      SetLastError(ERROR_TIMEOUT);
329
0
      goto return_false;
330
0
    }
331
332
0
    *lpNumberOfBytesRead = nbRead;
333
334
0
    EnterCriticalSection(&pComm->EventsLock);
335
0
    if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
336
0
    {
337
0
      if (pComm->eventChar != '\0' && memchr(lpBuffer, pComm->eventChar, nbRead))
338
0
        pComm->PendingEvents |= SERIAL_EV_RXCHAR;
339
0
    }
340
0
    LeaveCriticalSection(&pComm->EventsLock);
341
0
    goto return_true;
342
0
  }
343
344
0
  WINPR_ASSERT(FALSE);
345
0
  *lpNumberOfBytesRead = 0;
346
0
return_false:
347
0
  LeaveCriticalSection(&pComm->ReadLock);
348
0
  return FALSE;
349
0
return_true:
350
0
  LeaveCriticalSection(&pComm->ReadLock);
351
0
  return TRUE;
352
0
}
353
354
/**
355
 * ERRORS:
356
 *   ERROR_INVALID_HANDLE
357
 *   ERROR_NOT_SUPPORTED
358
 *   ERROR_INVALID_PARAMETER
359
 *   ERROR_BAD_DEVICE
360
 */
361
BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
362
                   LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
363
0
{
364
0
  WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
365
0
  struct timeval tmaxTimeout, *pTmaxTimeout;
366
0
  EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */
367
368
0
  if (!CommIsHandled(hDevice))
369
0
    goto return_false;
370
371
0
  if (lpOverlapped != NULL)
372
0
  {
373
0
    SetLastError(ERROR_NOT_SUPPORTED);
374
0
    goto return_false;
375
0
  }
376
377
0
  if (lpNumberOfBytesWritten == NULL)
378
0
  {
379
0
    SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
380
0
    goto return_false;
381
0
  }
382
383
0
  *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */
384
385
0
  if (nNumberOfBytesToWrite <= 0)
386
0
  {
387
0
    goto return_true; /* FIXME: or FALSE? */
388
0
  }
389
390
  /* FIXME: had expected eventfd_write() to return EAGAIN when
391
   * there is no eventfd_read() but this not the case. */
392
  /* discard a possible and no more relevant event */
393
0
  eventfd_read(pComm->fd_write_event, NULL);
394
  /* ms */
395
0
  ULONGLONG Tmax = nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier +
396
0
                   pComm->timeouts.WriteTotalTimeoutConstant;
397
  /* NB: select() may update the timeout argument to indicate
398
   * how much time was left. Keep the timeout variable out of
399
   * the while() */
400
0
  pTmaxTimeout = &tmaxTimeout;
401
0
  ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
402
403
0
  if (Tmax > 0)
404
0
  {
405
0
    pTmaxTimeout->tv_sec = Tmax / 1000;           /* s */
406
0
    pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
407
0
  }
408
0
  else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) &&
409
0
           (pComm->timeouts.WriteTotalTimeoutConstant == 0))
410
0
  {
411
0
    pTmaxTimeout = NULL;
412
0
  }
413
414
  /* else return immdiately */
415
416
0
  while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite)
417
0
  {
418
0
    int biggestFd = -1;
419
0
    fd_set event_set, write_set;
420
0
    int nbFds;
421
0
    biggestFd = pComm->fd_write;
422
423
0
    if (pComm->fd_write_event > biggestFd)
424
0
      biggestFd = pComm->fd_write_event;
425
426
0
    FD_ZERO(&event_set);
427
0
    FD_ZERO(&write_set);
428
0
    WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE);
429
0
    WINPR_ASSERT(pComm->fd_write < FD_SETSIZE);
430
0
    FD_SET(pComm->fd_write_event, &event_set);
431
0
    FD_SET(pComm->fd_write, &write_set);
432
0
    nbFds = select(biggestFd + 1, &event_set, &write_set, NULL, pTmaxTimeout);
433
434
0
    if (nbFds < 0)
435
0
    {
436
0
      CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno, strerror(errno));
437
0
      SetLastError(ERROR_IO_DEVICE);
438
0
      goto return_false;
439
0
    }
440
441
0
    if (nbFds == 0)
442
0
    {
443
      /* timeout */
444
0
      SetLastError(ERROR_TIMEOUT);
445
0
      goto return_false;
446
0
    }
447
448
    /* event_set */
449
450
0
    if (FD_ISSET(pComm->fd_write_event, &event_set))
451
0
    {
452
0
      eventfd_t event = 0;
453
454
0
      if (eventfd_read(pComm->fd_write_event, &event) < 0)
455
0
      {
456
0
        if (errno == EAGAIN)
457
0
        {
458
0
          WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
459
                               /* keep on */
460
0
        }
461
0
        else
462
0
        {
463
0
          CommLog_Print(WLOG_WARN,
464
0
                        "unexpected error on reading fd_write_event, errno=[%d] %s\n",
465
0
                        errno, strerror(errno));
466
          /* FIXME: goto return_false ? */
467
0
        }
468
469
0
        WINPR_ASSERT(errno == EAGAIN);
470
0
      }
471
472
0
      if (event == WINPR_PURGE_TXABORT)
473
0
      {
474
0
        SetLastError(ERROR_CANCELLED);
475
0
        goto return_false;
476
0
      }
477
478
0
      WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */
479
0
    }
480
481
    /* write_set */
482
483
0
    if (FD_ISSET(pComm->fd_write, &write_set))
484
0
    {
485
0
      ssize_t nbWritten;
486
0
      nbWritten = write(pComm->fd_write, ((const BYTE*)lpBuffer) + (*lpNumberOfBytesWritten),
487
0
                        nNumberOfBytesToWrite - (*lpNumberOfBytesWritten));
488
489
0
      if (nbWritten < 0)
490
0
      {
491
0
        CommLog_Print(WLOG_WARN,
492
0
                      "CommWriteFile failed after %" PRIu32
493
0
                      " bytes written, errno=[%d] %s\n",
494
0
                      *lpNumberOfBytesWritten, errno, strerror(errno));
495
496
0
        if (errno == EAGAIN)
497
0
        {
498
          /* keep on */
499
0
          continue;
500
0
        }
501
0
        else if (errno == EBADF)
502
0
        {
503
0
          SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
504
0
          goto return_false;
505
0
        }
506
0
        else
507
0
        {
508
0
          WINPR_ASSERT(FALSE);
509
0
          SetLastError(ERROR_IO_DEVICE);
510
0
          goto return_false;
511
0
        }
512
0
      }
513
514
0
      *lpNumberOfBytesWritten += nbWritten;
515
0
    }
516
0
  } /* while */
517
518
  /* FIXME: this call to tcdrain() doesn't look correct and
519
   * might hide a bug but was required while testing a serial
520
   * printer. Its driver was expecting the modem line status
521
   * SERIAL_MSR_DSR true after the sending which was never
522
   * happenning otherwise. A purge was also done before each
523
   * Write operation. The serial port was opened with:
524
   * DesiredAccess=0x0012019F. The printer worked fine with
525
   * mstsc. */
526
0
  tcdrain(pComm->fd_write);
527
528
0
return_true:
529
0
  LeaveCriticalSection(&pComm->WriteLock);
530
0
  return TRUE;
531
532
0
return_false:
533
0
  LeaveCriticalSection(&pComm->WriteLock);
534
0
  return FALSE;
535
0
}
536
537
#endif /* __linux__ */