1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS Timeout utility
4  * FILE:            base/applications/cmdutils/timeout/timeout.c
5  * PURPOSE:         An enhanced alternative to the Pause command.
6  * PROGRAMMERS:     Lee Schroeder (spaceseel at gmail dot com)
7  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8  */
9 
10 #include <stdio.h>
11 #include <stdlib.h>
12 
13 #include <windef.h>
14 #include <winbase.h>
15 #include <wincon.h>
16 #include <winuser.h>
17 
18 #include <conutils.h>
19 
20 #include "resource.h"
21 
22 VOID PrintError(DWORD dwError)
23 {
24     if (dwError == ERROR_SUCCESS)
25         return;
26 
27     ConMsgPuts(StdErr, FORMAT_MESSAGE_FROM_SYSTEM,
28                NULL, dwError, LANG_USER_DEFAULT);
29     ConPuts(StdErr, L"\n");
30 }
31 
32 BOOL
33 WINAPI
34 CtrlCIntercept(DWORD dwCtrlType)
35 {
36     switch (dwCtrlType)
37     {
38         case CTRL_C_EVENT:
39             ConPuts(StdOut, L"\n");
40             SetConsoleCtrlHandler(NULL, FALSE);
41             ExitProcess(EXIT_FAILURE);
42             return TRUE;
43     }
44     return FALSE;
45 }
46 
47 INT InputWait(BOOL bNoBreak, INT timerValue)
48 {
49     INT Status = EXIT_SUCCESS;
50     HANDLE hInput;
51     BOOL bUseTimer = (timerValue != -1);
52     HANDLE hTimer = NULL;
53     DWORD dwStartTime;
54     LONG timeElapsed;
55     DWORD dwWaitState;
56     INPUT_RECORD InputRecords[5];
57     ULONG NumRecords, i;
58     BOOL DisplayMsg = TRUE;
59     UINT WaitMsgId = (bNoBreak ? IDS_NOBREAK_INPUT : IDS_USER_INPUT);
60     UINT WaitCountMsgId = (bNoBreak ? IDS_NOBREAK_INPUT_COUNT : IDS_USER_INPUT_COUNT);
61 
62     /* Retrieve the current input handle */
63     hInput = ConStreamGetOSHandle(StdIn);
64     if (hInput == INVALID_HANDLE_VALUE)
65     {
66         ConResPrintf(StdErr, IDS_ERROR_INVALID_HANDLE_VALUE, GetLastError());
67         return EXIT_FAILURE;
68     }
69 
70     /* Start a new wait if we use the timer */
71     if (bNoBreak && bUseTimer)
72     {
73         hTimer = CreateWaitableTimer(NULL, TRUE, NULL);
74         if (hTimer == NULL)
75         {
76             /* A problem happened, bail out */
77             PrintError(GetLastError());
78             return EXIT_FAILURE;
79         }
80     }
81     if (bUseTimer)
82         dwStartTime = GetTickCount();
83 
84     /* If /NOBREAK is used, monitor for Ctrl-C input */
85     if (bNoBreak)
86         SetConsoleCtrlHandler(CtrlCIntercept, TRUE);
87 
88     /* Initially flush the console input queue to remove any pending events */
89     if (!GetNumberOfConsoleInputEvents(hInput, &NumRecords) ||
90         !FlushConsoleInputBuffer(hInput))
91     {
92         /* A problem happened, bail out */
93         PrintError(GetLastError());
94         Status = EXIT_FAILURE;
95         goto Quit;
96     }
97 
98     ConPuts(StdOut, L"\n");
99 
100     /* If the timer is not used, just show the message */
101     if (!bUseTimer)
102     {
103         ConPuts(StdOut, L"\r");
104         ConResPuts(StdOut, WaitMsgId);
105     }
106 
107     while (TRUE)
108     {
109         /* Decrease the timer if we use it */
110         if (bUseTimer)
111         {
112             /*
113              * Compute how much time the previous operations took.
114              * This allows us in particular to take account for any time
115              * elapsed if something slowed down, or if the console has been
116              * paused in the meantime.
117              */
118             timeElapsed = GetTickCount() - dwStartTime;
119             if (timeElapsed >= 1000)
120             {
121                 /* Increase dwStartTime by steps of 1 second */
122                 timeElapsed /= 1000;
123                 dwStartTime += (1000 * timeElapsed);
124 
125                 if (timeElapsed <= timerValue)
126                     timerValue -= timeElapsed;
127                 else
128                     timerValue = 0;
129 
130                 DisplayMsg = TRUE;
131             }
132 
133             if (DisplayMsg)
134             {
135                 ConPuts(StdOut, L"\r");
136                 ConResPrintf(StdOut, WaitCountMsgId, timerValue);
137                 ConPuts(StdOut, L" \b");
138 
139                 DisplayMsg = FALSE;
140             }
141 
142             /* Stop when the timer reaches zero */
143             if (timerValue <= 0)
144                 break;
145         }
146 
147         /* If /NOBREAK is used, only allow Ctrl-C input which is handled by the console handler */
148         if (bNoBreak)
149         {
150             if (bUseTimer)
151             {
152                 LARGE_INTEGER DueTime;
153 
154                 /* We use the timer: use a passive wait of maximum 1 second */
155                 timeElapsed = GetTickCount() - dwStartTime;
156                 if (timeElapsed < 1000)
157                 {
158                     /*
159                      * For whatever reason, x86 MSVC generates a ntdll!_allmul
160                      * call when using Int32x32To64(), instead of an imul
161                      * instruction. This leads the linker to error that _allmul
162                      * is missing, since we do not link against ntdll.
163                      * Everything is however OK with GCC...
164                      * We therefore use the __emul() intrinsic which does
165                      * the correct job.
166                      */
167                     DueTime.QuadPart = __emul(1000 - timeElapsed, -10000);
168                     SetWaitableTimer(hTimer, &DueTime, 0, NULL, NULL, FALSE);
169                     dwWaitState = WaitForSingleObject(hTimer, INFINITE);
170 
171                     /* Check whether the timer has been signaled */
172                     if (dwWaitState != WAIT_OBJECT_0)
173                     {
174                         /* An error happened, bail out */
175                         PrintError(GetLastError());
176                         Status = EXIT_FAILURE;
177                         break;
178                     }
179                 }
180             }
181             else
182             {
183                 /* No timer is used: wait indefinitely */
184                 Sleep(INFINITE);
185             }
186 
187             continue;
188         }
189 
190         /* /NOBREAK is not used, check for user key presses */
191 
192         /*
193          * If the timer is used, use a passive wait of maximum 1 second
194          * while monitoring for incoming console input events, so that
195          * we are still able to display the timing count.
196          * Indeed, ReadConsoleInputW() indefinitely waits until an input
197          * event appears. ReadConsoleInputW() is however used to retrieve
198          * the input events where there are some, as well as for waiting
199          * indefinitely in case we do not use the timer.
200          */
201         if (bUseTimer)
202         {
203             /* Wait a maximum of 1 second for input events */
204             timeElapsed = GetTickCount() - dwStartTime;
205             if (timeElapsed < 1000)
206                 dwWaitState = WaitForSingleObject(hInput, 1000 - timeElapsed);
207             else
208                 dwWaitState = WAIT_TIMEOUT;
209 
210             /* Check whether the input event has been signaled, or a timeout happened */
211             if (dwWaitState == WAIT_TIMEOUT)
212                 continue;
213             if (dwWaitState != WAIT_OBJECT_0)
214             {
215                 /* An error happened, bail out */
216                 PrintError(GetLastError());
217                 Status = EXIT_FAILURE;
218                 break;
219             }
220 
221             /* Be sure there is something in the console input queue */
222             if (!PeekConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
223             {
224                 /* An error happened, bail out */
225                 ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
226                 Status = EXIT_FAILURE;
227                 break;
228             }
229 
230             if (NumRecords == 0)
231                 continue;
232         }
233 
234         /*
235          * Some events have been detected, pop them out from the input queue.
236          * In case we do not use the timer, wait indefinitely until an input
237          * event appears.
238          */
239         if (!ReadConsoleInputW(hInput, InputRecords, ARRAYSIZE(InputRecords), &NumRecords))
240         {
241             /* An error happened, bail out */
242             ConResPrintf(StdErr, IDS_ERROR_READ_INPUT, GetLastError());
243             Status = EXIT_FAILURE;
244             break;
245         }
246 
247         /* Check the input events for a key press */
248         for (i = 0; i < NumRecords; ++i)
249         {
250             /* Ignore any non-key event */
251             if (InputRecords[i].EventType != KEY_EVENT)
252                 continue;
253 
254             /* Ignore any system key event */
255             if ((InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_CONTROL) ||
256              // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_ALT_PRESSED  | RIGHT_ALT_PRESSED )) ||
257              // (InputRecords[i].Event.KeyEvent.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED)) ||
258                 (InputRecords[i].Event.KeyEvent.wVirtualKeyCode == VK_MENU))
259             {
260                 continue;
261             }
262 
263             /* This is a non-system key event, stop waiting */
264             goto Stop;
265         }
266     }
267 
268 Stop:
269     ConPuts(StdOut, L"\n");
270 
271 Quit:
272     if (bNoBreak)
273         SetConsoleCtrlHandler(NULL, FALSE);
274 
275     if (bNoBreak && bUseTimer)
276         CloseHandle(hTimer);
277 
278     return Status;
279 }
280 
281 int wmain(int argc, WCHAR* argv[])
282 {
283     INT timerValue = -1;
284     PWCHAR pszNext;
285     BOOL bDisableInput = FALSE, fTimerFlags = 0;
286     int index = 0;
287 
288     /* Initialize the Console Standard Streams */
289     ConInitStdStreams();
290 
291     if (argc == 1)
292     {
293         ConResPrintf(StdOut, IDS_USAGE);
294         return EXIT_SUCCESS;
295     }
296 
297     /* Parse the command line for options */
298     for (index = 1; index < argc; index++)
299     {
300         if (argv[index][0] == L'-' || argv[index][0] == L'/')
301         {
302             switch (towupper(argv[index][1]))
303             {
304                 case L'?': /* Help */
305                 {
306                     ConResPrintf(StdOut, IDS_USAGE);
307                     return EXIT_SUCCESS;
308                 }
309 
310                 case L'T': /* Timer */
311                 {
312                     /* Consecutive /T switches are invalid */
313                     if (fTimerFlags & 2)
314                     {
315                         ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
316                         return EXIT_FAILURE;
317                     }
318 
319                     /* Remember that a /T switch has been encountered */
320                     fTimerFlags |= 2;
321 
322                     /* Go to the next (timer) value */
323                     continue;
324                 }
325             }
326 
327             /* This flag is used to ignore any keyboard keys but Ctrl-C */
328             if (_wcsicmp(&argv[index][1], L"NOBREAK") == 0)
329             {
330                 bDisableInput = TRUE;
331 
332                 /* Go to next value */
333                 continue;
334             }
335         }
336 
337         /* The timer value can also be specified without the /T switch */
338 
339         /* Only one timer value is supported */
340         if (fTimerFlags & 1)
341         {
342             ConResPrintf(StdErr, IDS_ERROR_ONE_TIME);
343             return EXIT_FAILURE;
344         }
345 
346         timerValue = wcstol(argv[index], &pszNext, 10);
347         if (*pszNext)
348         {
349             ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
350             return EXIT_FAILURE;
351         }
352 
353         /* Remember that the timer value has been set */
354         fTimerFlags |= 1;
355     }
356 
357     /* A timer value is mandatory in order to continue */
358     if (!(fTimerFlags & 1))
359     {
360         ConResPrintf(StdErr, IDS_ERROR_NO_TIMER_VALUE);
361         return EXIT_FAILURE;
362     }
363 
364     /* Make sure the timer value is within range */
365     if ((timerValue < -1) || (timerValue > 99999))
366     {
367         ConResPrintf(StdErr, IDS_ERROR_OUT_OF_RANGE);
368         return EXIT_FAILURE;
369     }
370 
371     return InputWait(bDisableInput, timerValue);
372 }
373