File Coverage

secret_buffer_wait_readable.c
Criterion Covered Total %
statement 19 27 70.3
branch 10 20 50.0
condition n/a
subroutine n/a
pod n/a
total 29 47 61.7


line stmt bran cond sub pod time code
1             /* Implementation of sb_wait_fh_readable and sb_wait_fd_readable, used by the
2             * $sb->append_console_line(timeout => $t) feature.
3             *
4             * This is basically a cross-platform version of select(). It waits for a handle to become
5             * readable up to a caller-specified timeout. The caller can then most likely perform a read
6             * without blocking. On every OS but Win32, this is as easy as a select() call. On Win32,
7             * the goal is in fact *impossible* for arbitrary user-supplied handles; the asynchronous
8             * "overlapped i/o" offered by Win32 requires a handle to be initially created with that
9             * feature enabled. Lacking the overlapped flag, checking a handle for readability has to be
10             * implemented differently for each type of handle.
11             * - Sockets : use select()
12             * - Real Files: assume it doesn't block and return true
13             * - Pipes : poll in a loop with PeekNamedPipe
14             * - Console : PeekConsoleInput to look for key events
15             * I'm ignoring serial ports for now since I don't have an easy way to test.
16             */
17              
18 34           static struct timeval* sb_timeout_sv_to_timeval(pTHX_ SV *timeout_sv, struct timeval *tv_out) {
19 34 50         if (timeout_sv && SvOK(timeout_sv)) {
    50          
20 34           NV timeout = SvNV(timeout_sv);
21 34 50         if (timeout < 0)
22 0           croak("timeout must be >= 0");
23              
24 34           tv_out->tv_sec = (long) timeout;
25 34           tv_out->tv_usec = (long) ((timeout - (NV) tv_out->tv_sec) * 1000000.0);
26 34 50         if (tv_out->tv_usec < 0)
27 0           tv_out->tv_usec = 0;
28 34 50         if (tv_out->tv_usec >= 1000000) {
29 0           tv_out->tv_sec += tv_out->tv_usec / 1000000;
30 0           tv_out->tv_usec %= 1000000;
31             }
32 34           return tv_out;
33             }
34 0           return NULL;
35             }
36              
37             #ifdef WIN32
38              
39             static DWORD sb_win32_timeout_sv_to_ms(pTHX_ SV *timeout_sv) {
40             DWORD wait_ms= INFINITE;
41             /* The value INFINITE is 0xFFFFFFFF, so the timeout must be less than that constant. */
42              
43             if (timeout_sv && SvOK(timeout_sv)) {
44             NV timeout = SvNV(timeout_sv);
45             if (timeout < 0)
46             croak("timeout must be >= 0");
47             if (timeout > (NV)(0xFFFFFFFD * 0.001))
48             wait_ms = 0xFFFFFFFD;
49             else
50             wait_ms = (DWORD) (timeout * 1000.0 + 0.5);
51             }
52              
53             return wait_ms;
54             }
55              
56             /* For console input handles, they appear ready when *any* event is pending. The next ReadFile
57             * will discard any non-character events and block until it receives a character event.
58             * Note that an edge case of codepage 65001 (UTF-8) is that if one keypress generates multiple
59             * bytes and you only read one byte, the rest of the bytes are readable without blocking but
60             * there is literally no way to discover that status. This will end up waiting for the
61             * following keypress before returning true.
62             */
63             static bool
64             sb_wait_win32_console_readable(HANDLE hdl, DWORD wait_ms) {
65             while (1) {
66             INPUT_RECORD in_rec[16];
67             DWORD i, nread;
68             DWORD ready;
69              
70             ready = WaitForSingleObject(hdl, wait_ms);
71             if (ready == WAIT_TIMEOUT)
72             return false;
73             if (ready != WAIT_OBJECT_0)
74             croak_with_syserror("WaitForSingleObject failed", GetLastError());
75              
76             /* After the first blocking wait, drain any non-character events and then
77             * return, rather than trying to calculate how much time is left.
78             * The caller should always expect the possibility of an early return.
79             */
80             if (wait_ms != INFINITE)
81             wait_ms = 0;
82              
83             /* Inspect pending console events until we find a real
84             * character-producing key event. Discard non-character events so we
85             * don't wake forever on the same unread record.
86             */
87             if (PeekConsoleInputW(hdl, in_rec, sizeof(in_rec)/sizeof(*in_rec), &nread)) {
88             for (i = 0; i < nread; i++) {
89             if (in_rec[i].EventType == KEY_EVENT
90             && in_rec[i].Event.KeyEvent.bKeyDown
91             && in_rec[i].Event.KeyEvent.uChar.UnicodeChar != 0
92             )
93             break;
94             }
95             secret_buffer_wipe((char*) in_rec, sizeof(in_rec));
96              
97             /* discard the non-char events */
98             if (i > 0) {
99             DWORD nread2;
100             if (!ReadConsoleInputW(hdl, in_rec, i, &nread2))
101             croak_with_syserror("ReadConsoleInput failed", GetLastError());
102             secret_buffer_wipe((char*) in_rec, sizeof(in_rec));
103             }
104             if (i == nread)
105             continue;
106             }
107             else {
108             SetLastError(0);
109             }
110              
111             return true;
112             }
113             }
114              
115             /* There doesn't seem to be any better way to do this than polling at short intervals
116             * until PeekNamedPipe gets some data. This is terrible but I'm done wasting time on it.
117             */
118             static bool sb_wait_win32_pipe_readable(HANDLE hdl, DWORD wait_ms) {
119             DWORD start_tick = 0;
120              
121             if (wait_ms != INFINITE) {
122             /* Don't allow huge values of wait_ms lest it wrap while we were sleeping.
123             * The API contract is that we wait *up to* than this number, anyway. */
124             if (wait_ms > 0xFFFF0000)
125             wait_ms= 0xFFFF0000;
126             start_tick = GetTickCount();
127             }
128              
129             while (1) {
130             DWORD avail = 0;
131              
132             if (!PeekNamedPipe(hdl, NULL, 0, NULL, &avail, NULL)) {
133             DWORD err = GetLastError();
134              
135             /* Broken or disconnected pipe: a subsequent read should complete
136             * immediately with EOF/failure rather than block.
137             */
138             if (err == ERROR_BROKEN_PIPE || err == ERROR_PIPE_NOT_CONNECTED)
139             return true;
140             croak_with_syserror("PeekNamedPipe failed", err);
141             }
142              
143             if (avail > 0)
144             return true;
145              
146             if (wait_ms == 0)
147             return false;
148              
149             if (wait_ms == INFINITE) {
150             Sleep(5);
151             }
152             else {
153             DWORD elapsed = GetTickCount() - start_tick;
154             DWORD sleep_ms;
155              
156             if (elapsed >= wait_ms)
157             return false;
158             sleep_ms = wait_ms - elapsed;
159             Sleep(sleep_ms > 5 ? 5 : sleep_ms);
160             }
161             }
162             }
163              
164             /* Given a Win32 HANDLE, dispatch to the function that can wait for
165             * readability for that type of handle.
166             */
167             bool sb_wait_win32_handle_readable(pTHX_ HANDLE hdl, SV *timeout_sv) {
168             DWORD ftype;
169              
170             SetLastError(0);
171             ftype = GetFileType(hdl);
172             if (ftype == FILE_TYPE_UNKNOWN && GetLastError() != NO_ERROR)
173             croak_with_syserror("GetFileType failed", GetLastError());
174              
175             switch (ftype) {
176             case FILE_TYPE_DISK:
177             /* Regular files are effectively always readable for our purposes. */
178             return true;
179              
180             case FILE_TYPE_CHAR: {
181             DWORD console_mode;
182              
183             /* Only console handles are supported here. Other character devices are not. */
184             if (!GetConsoleMode(hdl, &console_mode)) {
185             SetLastError(0);
186             croak("timeout is not supported on this type of Win32 character handle");
187             }
188              
189             return sb_wait_win32_console_readable(hdl, sb_win32_timeout_sv_to_ms(aTHX_ timeout_sv));
190             }
191              
192             case FILE_TYPE_PIPE: {
193             DWORD avail= 0;
194             /* both named pipes and winsock SOCKETs are reported as TYPE_PIPE */
195             if (PeekNamedPipe(hdl, NULL, 0, NULL, &avail, NULL)) {
196             if (avail > 0)
197             return true;
198             return sb_wait_win32_pipe_readable(hdl, sb_win32_timeout_sv_to_ms(aTHX_ timeout_sv));
199             }
200             else if (GetLastError() != ERROR_INVALID_FUNCTION)
201             croak_with_syserror("PeekNamedPipe failed", GetLastError());
202             /* else fall through because it wasn't really a pipe */
203             }
204             default:
205             croak("timeout is not supported on this type of Win32 handle");
206             }
207             }
208             #endif
209              
210 34           static bool sb_wait_fd_readable(pTHX_ int fd, SV *timeout_sv) {
211             #ifdef WIN32
212             /* Win32 getsockopt() and select() use SOCKET pointers, but Perl has defined
213             * macros so that we use the regular POSIX style API, and can share code below.
214             */
215             int val, len = sizeof(val);
216             int save_errno= errno;
217             int ret= getsockopt(fd, SOL_SOCKET, SO_TYPE, (char*)&val, &len);
218             errno= save_errno;
219             if (ret < 0) {
220             /* Not a socket, so need to use something other than 'select' */
221             HANDLE hFile = (HANDLE)_get_osfhandle(fd);
222             if (hFile == INVALID_HANDLE_VALUE)
223             croak("Handle has no system file descriptor");
224             return sb_wait_win32_handle_readable(aTHX_ hFile, timeout_sv);
225             } else
226             #endif
227             {
228             fd_set rfds;
229             int r;
230             struct timeval tv;
231              
232 578 100         FD_ZERO(&rfds);
233 34           FD_SET(fd, &rfds);
234              
235 34           r = select(fd + 1, &rfds, NULL, NULL, sb_timeout_sv_to_timeval(aTHX_ timeout_sv, &tv));
236 34 50         if (r < 0 && errno != EINTR)
    0          
237 0           croak_with_syserror("select failed", errno);
238 34           return (r > 0);
239             }
240             }
241              
242 34           static bool sb_wait_fh_readable(pTHX_ PerlIO *fh, SV *timeout_sv) {
243             int fd;
244 34 50         if (PerlIO_get_cnt(fh) > 0)
245 0           return true;
246 34 50         if ((fd= PerlIO_fileno(fh)) < 0)
247 0           croak("Handle has no system file descriptor (fileno)");
248 34           return sb_wait_fd_readable(aTHX_ fd, timeout_sv);
249             }