2 * CertViewer - tee for Windows
3 * Copyright (c) 2023 "dEajL3kA" <Cumpoing79@web.de>
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6 * associated documentation files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
8 * sub license, and/or sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions: The above copyright notice and this
10 * permission notice shall be included in all copies or substantial portions of the Software.
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
16 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 #define WIN32_LEAN_AND_MEAN 1
22 #define BUFFSIZE 4096U
23 #pragma warning(disable: 4706)
25 // --------------------------------------------------------------------------
27 // --------------------------------------------------------------------------
29 static BOOL
is_terminal(const HANDLE handle
)
32 return GetConsoleMode(handle
, &mode
);
35 static wchar_t *concat_3(const wchar_t *const strA
, const wchar_t *const strB
, const wchar_t *const strC
)
37 const size_t lenA
= lstrlenW(strA
), lenB
= lstrlenW(strB
), lenC
= lstrlenW(strC
);
38 wchar_t *const buffer
= (wchar_t*) LocalAlloc(LPTR
, sizeof(wchar_t) * (lenA
+ lenB
+ lenC
+ 1U));
41 lstrcpyW(buffer
, strA
);
42 lstrcpyW(buffer
+ lenA
, strB
);
43 lstrcpyW(buffer
+ lenA
+ lenB
, strC
);
48 #define SAFE_CLOSE_HANDLE(HANDLE) do \
52 CloseHandle((HANDLE)); \
57 // --------------------------------------------------------------------------
58 // Console CTRL+C handler
59 // --------------------------------------------------------------------------
61 static volatile BOOL g_stop
= FALSE
;
63 static BOOL WINAPI
console_handler(const DWORD ctrlType
)
68 case CTRL_BREAK_EVENT
:
69 case CTRL_CLOSE_EVENT
:
77 // --------------------------------------------------------------------------
79 // --------------------------------------------------------------------------
81 static char *utf16_to_utf8(const wchar_t *const input
)
83 const int buff_size
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, NULL
, 0, NULL
, NULL
);
86 char *const buffer
= (char*)LocalAlloc(LPTR
, buff_size
);
89 const int result
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, buffer
, buff_size
, NULL
, NULL
);
90 if ((result
> 0) && (result
<= buff_size
))
100 static BOOL
write_text(const HANDLE handle
, const wchar_t *const text
)
104 if (GetConsoleMode(handle
, &written
))
106 result
= WriteConsoleW(handle
, text
, lstrlenW(text
), &written
, NULL
);
110 char *const utf8_text
= utf16_to_utf8(text
);
113 result
= WriteFile(handle
, utf8_text
, lstrlenA(utf8_text
), &written
, NULL
);
114 LocalFree(utf8_text
);
120 // --------------------------------------------------------------------------
122 // --------------------------------------------------------------------------
124 static BYTE buffer
[2U][BUFFSIZE
];
125 static DWORD bytesTotal
[2U] = { 0U, 0U };
126 static volatile ULONG_PTR index
= 0U;
130 HANDLE hOutput
,hError
;
132 HANDLE hEventReady
[2U], hEventCompleted
;
136 static DWORD WINAPI
writer_thread_start_routine(const LPVOID lpThreadParameter
)
139 const thread_t
*const param
= (thread_t
*) lpThreadParameter
;
143 switch (WaitForMultipleObjects(2U, param
->hEventReady
, FALSE
, INFINITE
))
147 case WAIT_OBJECT_0
+ 1U:
148 SetEvent(param
->hEventCompleted
);
151 write_text(param
->hError
, L
"[tee] System error: Failed to wait for event!\n");
155 const ULONG_PTR myIndex
= index
;
157 for (DWORD offset
= 0U; offset
< bytesTotal
[myIndex
]; offset
+= bytesWritten
)
159 const BOOL result
= WriteFile(param
->hOutput
, buffer
[myIndex
] + offset
, bytesTotal
[myIndex
] - offset
, &bytesWritten
, NULL
);
160 if ((!result
) || (!bytesWritten
))
162 write_text(param
->hError
, L
"[tee] Error: Not all data could be written!\n");
167 SetEvent(param
->hEventCompleted
);
171 FlushFileBuffers(param
->hOutput
);
176 // --------------------------------------------------------------------------
178 // --------------------------------------------------------------------------
180 int wmain(const int argc
, const wchar_t *const argv
[])
182 HANDLE hThreads
[2U] = { NULL
, NULL
};
183 HANDLE hEventStop
= NULL
, hEventThrdReady
[2U] = { NULL
, NULL
}, hEventCompleted
[2U] = { NULL
, NULL
};
184 HANDLE hMyFile
= INVALID_HANDLE_VALUE
;
185 int exitCode
= 1, argOff
= 1;
186 BOOL append
= FALSE
, flush
= FALSE
, ignore
= FALSE
;
187 thread_t threadData
[2U];
189 /* Initialize standard streams */
190 const HANDLE hStdIn
= GetStdHandle(STD_INPUT_HANDLE
), hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
), hStdErr
= GetStdHandle(STD_ERROR_HANDLE
);
191 if ((hStdIn
== INVALID_HANDLE_VALUE
) || (hStdOut
== INVALID_HANDLE_VALUE
) || (hStdErr
== INVALID_HANDLE_VALUE
))
196 /* Set up CRTL+C handler */
197 SetConsoleCtrlHandler(console_handler
, TRUE
);
200 if ((argc
< 2) || (lstrcmpiW(argv
[1], L
"/?") == 0) || (lstrcmpiW(argv
[1], L
"--help") == 0))
202 write_text(hStdErr
, L
"tee for Windows [" TEXT(__DATE__
) L
"]\n\n");
203 write_text(hStdErr
, L
"Usage:\n");
204 write_text(hStdErr
, L
" your_program.exe [...] | tee.exe [options] <output_file>\n\n");
205 write_text(hStdErr
, L
"Options:\n");
206 write_text(hStdErr
, L
" --append Append to the existing file, instead of truncating\n");
207 write_text(hStdErr
, L
" --flush Flush output file after each write operation\n");
208 write_text(hStdErr
, L
" --ignore Ignore the interrupt signal (SIGINT), e.g. CTRL+C\n\n");
212 /* Parse command-line options */
213 while (argOff
< argc
)
215 if ((argv
[argOff
][0U] == L
'-') && (argv
[argOff
][1U] == L
'-'))
217 const wchar_t *const option
= argv
[argOff
++] + 2U;
218 if (*option
== L
'\0')
222 else if (lstrcmpiW(option
, L
"append") == 0)
226 else if (lstrcmpiW(option
, L
"flush") == 0)
230 else if (lstrcmpiW(option
, L
"ignore") == 0)
236 wchar_t *const message
= concat_3(L
"[tee] Error: Invalid option \"--", option
, L
"\" encountered!\n");
239 write_text(hStdErr
, message
);
247 break; /* stop option processing */
251 /* Check output file name */
254 write_text(hStdErr
, L
"[tee] Error: Output file name is missing!\n");
259 if (!(hEventStop
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
)))
261 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
264 for (size_t i
= 0U; i
< 2U; ++i
)
266 if (!(hEventThrdReady
[i
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
268 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
271 if (!(hEventCompleted
[i
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
273 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
278 /* Open output file */
279 if ((hMyFile
= CreateFileW(argv
[argOff
], GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, append
? OPEN_ALWAYS
: CREATE_ALWAYS
, 0U, NULL
)) == INVALID_HANDLE_VALUE
)
281 write_text(hStdErr
, L
"[tee] Error: Failed to open the output file for writing!\n");
285 /* Seek to the end of the file */
288 LARGE_INTEGER offset
= { .QuadPart
= 0LL };
289 if (!SetFilePointerEx(hMyFile
, offset
, NULL
, FILE_END
))
291 write_text(hStdErr
, L
"[tee] Error: Failed to move the file pointer to the end of the file!\n");
296 /* Set up thread data */
297 for (size_t i
= 0; i
< 2U; ++i
)
299 threadData
[i
].hOutput
= (i
> 0U) ? hMyFile
: hStdOut
;
300 threadData
[i
].hError
= hStdErr
;
301 threadData
[i
].flush
= flush
&& (!is_terminal(threadData
[i
].hOutput
));
302 threadData
[i
].hEventReady
[0U] = hEventThrdReady
[i
];
303 threadData
[i
].hEventReady
[1U] = hEventStop
;
304 threadData
[i
].hEventCompleted
= hEventCompleted
[i
];
308 for (size_t i
= 0; i
< 2U; ++i
)
310 if (!(hThreads
[i
] = CreateThread(NULL
, 0U, writer_thread_start_routine
, &threadData
[i
], 0U, NULL
)))
312 write_text(hStdErr
, L
"[tee] System error: Failed to create thread!\n");
317 /* Are we reading from a pipe? */
318 const BOOL isPipeInput
= (GetFileType(hStdIn
) == FILE_TYPE_PIPE
);
320 /* Initialize index */
321 ULONG_PTR myIndex
= 1U - index
;
323 /* Process all input from STDIN stream */
326 for (size_t i
= 0U; i
< 2U; ++i
)
328 if (!SetEvent(hEventThrdReady
[i
]))
330 write_text(hStdErr
, L
"[tee] System error: Failed to signal event!\n");
335 if (!ReadFile(hStdIn
, buffer
[myIndex
], BUFFSIZE
, &bytesTotal
[myIndex
], NULL
))
337 if (GetLastError() != ERROR_BROKEN_PIPE
)
339 write_text(hStdErr
, L
"[tee] Error: Failed to read input data!\n");
345 if ((!bytesTotal
[myIndex
]) && (!isPipeInput
)) /*pipes may return zero bytes, even when more data can become available later!*/
350 const DWORD waitResult
= WaitForMultipleObjects(2U, hEventCompleted
, TRUE
, INFINITE
);
351 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
353 write_text(hStdErr
, L
"[tee] System error: Failed to wait for events!\n");
357 myIndex
= (ULONG_PTR
) InterlockedExchangePointer((PVOID
*)&index
, (PVOID
)myIndex
);
359 while ((!g_stop
) || ignore
);
365 /* Stop the worker threads */
368 SetEvent(hEventStop
);
371 /* Wait for worker threads to exit */
374 const DWORD waitResult
= WaitForMultipleObjects(hThreads
[1U] ? 2U : 1U, hThreads
, TRUE
, 12000U);
375 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
377 for (DWORD i
= 0U; i
< (hThreads
[1U] ? 2U : 1U); ++i
)
379 if (WaitForSingleObject(hThreads
[i
], 125U) != WAIT_OBJECT_0
)
381 write_text(hStdErr
, L
"[tee] Error: Worker thread did not exit cleanly!\n");
382 TerminateThread(hThreads
[i
], 1U);
388 /* Close worker threads */
389 for (DWORD i
= 0U; i
< 2U; ++i
)
391 SAFE_CLOSE_HANDLE(hThreads
[i
]);
394 /* Close output file */
395 if (hMyFile
!= INVALID_HANDLE_VALUE
)
399 FlushFileBuffers(hMyFile
);
401 CloseHandle(hMyFile
);
405 for (size_t i
= 0U; i
< 2U; ++i
)
407 SAFE_CLOSE_HANDLE(hEventThrdReady
[i
]);
408 SAFE_CLOSE_HANDLE(hEventCompleted
[i
]);
410 SAFE_CLOSE_HANDLE(hEventStop
);
416 // --------------------------------------------------------------------------
418 // --------------------------------------------------------------------------
420 #pragma warning(disable: 4702)
422 int wmainCRTStartup(void)
424 SetErrorMode(SEM_FAILCRITICALERRORS
);
427 LPWSTR
*const szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
433 const int retval
= wmain(nArgs
, szArglist
);
434 LocalFree(szArglist
);
435 ExitProcess((UINT
)retval
);