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 #define APPEND_STRING(X) \
36 do { lstrcpyW(ptr, str##X); ptr += len##X; } while(0)
38 static wchar_t *concat_3(const wchar_t *const strA
, const wchar_t *const strB
, const wchar_t *const strC
)
40 const size_t lenA
= lstrlenW(strA
), lenB
= lstrlenW(strB
), lenC
= lstrlenW(strC
);
41 wchar_t *const buffer
= (wchar_t*) LocalAlloc(LPTR
, sizeof(wchar_t) * (lenA
+ lenB
+ lenC
+ 1U));
44 wchar_t *ptr
= buffer
;
52 #define SAFE_CLOSE_HANDLE(HANDLE) do \
56 CloseHandle((HANDLE)); \
61 // --------------------------------------------------------------------------
62 // Console CTRL+C handler
63 // --------------------------------------------------------------------------
65 static volatile BOOL g_stop
= FALSE
;
67 static BOOL WINAPI
console_handler(const DWORD ctrlType
)
72 case CTRL_BREAK_EVENT
:
73 case CTRL_CLOSE_EVENT
:
81 // --------------------------------------------------------------------------
83 // --------------------------------------------------------------------------
85 static ULONGLONG
get_version(void)
87 const HRSRC hVersion
= FindResourceW(NULL
, MAKEINTRESOURCE(VS_VERSION_INFO
), RT_VERSION
);
90 const HGLOBAL hResource
= LoadResource(NULL
, hVersion
);
93 const DWORD sizeOfResource
= SizeofResource(NULL
, hResource
);
94 if (sizeOfResource
>= sizeof(VS_FIXEDFILEINFO
))
96 const PVOID addrResourceBlock
= LockResource(hResource
);
97 if (addrResourceBlock
)
99 VS_FIXEDFILEINFO
*fileInfoData
;
101 if (VerQueryValueW(addrResourceBlock
, L
"\\", &fileInfoData
, &fileInfoSize
))
103 ULARGE_INTEGER fileVersion
;
104 fileVersion
.LowPart
= fileInfoData
->dwFileVersionLS
;
105 fileVersion
.HighPart
= fileInfoData
->dwFileVersionMS
;
106 return fileVersion
.QuadPart
;
116 static const wchar_t *get_version_string(void)
118 static wchar_t text
[64U] = { '\0' };
119 lstrcpyW(text
, L
"tee for Windows v#.#.# [" TEXT(__DATE__
) L
"]\n");
121 const ULONGLONG version
= get_version();
124 text
[17U] = L
'0' + ((version
>> 48) & 0xFFFF);
125 text
[19U] = L
'0' + ((version
>> 32) & 0xFFFF);
126 text
[21U] = L
'0' + ((version
>> 16) & 0xFFFF);
132 // --------------------------------------------------------------------------
134 // --------------------------------------------------------------------------
136 static char *utf16_to_utf8(const wchar_t *const input
)
138 const int buff_size
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, NULL
, 0, NULL
, NULL
);
141 char *const buffer
= (char*)LocalAlloc(LPTR
, buff_size
);
144 const int result
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, buffer
, buff_size
, NULL
, NULL
);
145 if ((result
> 0) && (result
<= buff_size
))
155 static BOOL
write_text(const HANDLE handle
, const wchar_t *const text
)
159 if (GetConsoleMode(handle
, &written
))
161 result
= WriteConsoleW(handle
, text
, lstrlenW(text
), &written
, NULL
);
165 char *const utf8_text
= utf16_to_utf8(text
);
168 result
= WriteFile(handle
, utf8_text
, lstrlenA(utf8_text
), &written
, NULL
);
169 LocalFree(utf8_text
);
175 // --------------------------------------------------------------------------
177 // --------------------------------------------------------------------------
179 static BYTE buffer
[2U][BUFFSIZE
];
180 static DWORD bytesTotal
[2U] = { 0U, 0U };
181 static volatile ULONG_PTR index
= 0U;
185 HANDLE hOutput
,hError
;
187 HANDLE hEventReady
[2U], hEventCompleted
;
191 static DWORD WINAPI
writer_thread_start_routine(const LPVOID lpThreadParameter
)
194 const thread_t
*const param
= (thread_t
*) lpThreadParameter
;
198 switch (WaitForMultipleObjects(2U, param
->hEventReady
, FALSE
, INFINITE
))
202 case WAIT_OBJECT_0
+ 1U:
203 SetEvent(param
->hEventCompleted
);
206 write_text(param
->hError
, L
"[tee] System error: Failed to wait for event!\n");
210 const ULONG_PTR myIndex
= index
;
212 for (DWORD offset
= 0U; offset
< bytesTotal
[myIndex
]; offset
+= bytesWritten
)
214 const BOOL result
= WriteFile(param
->hOutput
, buffer
[myIndex
] + offset
, bytesTotal
[myIndex
] - offset
, &bytesWritten
, NULL
);
215 if ((!result
) || (!bytesWritten
))
217 write_text(param
->hError
, L
"[tee] Error: Not all data could be written!\n");
222 SetEvent(param
->hEventCompleted
);
226 FlushFileBuffers(param
->hOutput
);
231 // --------------------------------------------------------------------------
233 // --------------------------------------------------------------------------
235 int wmain(const int argc
, const wchar_t *const argv
[])
237 HANDLE hThreads
[2U] = { NULL
, NULL
};
238 HANDLE hEventStop
= NULL
, hEventThrdReady
[2U] = { NULL
, NULL
}, hEventCompleted
[2U] = { NULL
, NULL
};
239 HANDLE hMyFile
= INVALID_HANDLE_VALUE
;
240 int exitCode
= 1, argOff
= 1;
241 BOOL append
= FALSE
, flush
= FALSE
, ignore
= FALSE
;
242 thread_t threadData
[2U];
244 /* Initialize standard streams */
245 const HANDLE hStdIn
= GetStdHandle(STD_INPUT_HANDLE
), hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
), hStdErr
= GetStdHandle(STD_ERROR_HANDLE
);
246 if ((hStdIn
== INVALID_HANDLE_VALUE
) || (hStdOut
== INVALID_HANDLE_VALUE
) || (hStdErr
== INVALID_HANDLE_VALUE
))
251 /* Set up CRTL+C handler */
252 SetConsoleCtrlHandler(console_handler
, TRUE
);
255 if ((argc
> 1) && (lstrcmpiW(argv
[1], L
"--version") == 0))
257 write_text(hStdErr
, get_version_string());
262 if ((argc
< 2) || (lstrcmpiW(argv
[1], L
"/?") == 0) || (lstrcmpiW(argv
[1], L
"--help") == 0))
264 write_text(hStdErr
, get_version_string());
265 write_text(hStdErr
, L
"\n"
267 L
" your_program.exe [...] | tee.exe [options] <output_file>\n\n"
269 L
" --append Append to the existing file, instead of truncating\n"
270 L
" --flush Flush output file after each write operation\n"
271 L
" --ignore Ignore the interrupt signal (SIGINT), e.g. CTRL+C\n\n");
275 /* Parse command-line options */
276 while (argOff
< argc
)
278 if ((argv
[argOff
][0U] == L
'-') && (argv
[argOff
][1U] == L
'-'))
280 const wchar_t *const option
= argv
[argOff
++] + 2U;
281 if (*option
== L
'\0')
285 else if (lstrcmpiW(option
, L
"append") == 0)
289 else if (lstrcmpiW(option
, L
"flush") == 0)
293 else if (lstrcmpiW(option
, L
"ignore") == 0)
299 wchar_t *const message
= concat_3(L
"[tee] Error: Invalid option \"--", option
, L
"\" encountered!\n");
302 write_text(hStdErr
, message
);
310 break; /* stop option processing */
314 /* Check output file name */
317 write_text(hStdErr
, L
"[tee] Error: Output file name is missing!\n");
322 if (!(hEventStop
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
)))
324 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
327 for (size_t i
= 0U; i
< 2U; ++i
)
329 if (!(hEventThrdReady
[i
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
331 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
334 if (!(hEventCompleted
[i
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
336 write_text(hStdErr
, L
"[tee] System error: Failed to create event!\n\n");
341 /* Open output file */
342 if ((hMyFile
= CreateFileW(argv
[argOff
], GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, append
? OPEN_ALWAYS
: CREATE_ALWAYS
, 0U, NULL
)) == INVALID_HANDLE_VALUE
)
344 write_text(hStdErr
, L
"[tee] Error: Failed to open the output file for writing!\n");
348 /* Seek to the end of the file */
351 LARGE_INTEGER offset
= { .QuadPart
= 0LL };
352 if (!SetFilePointerEx(hMyFile
, offset
, NULL
, FILE_END
))
354 write_text(hStdErr
, L
"[tee] Error: Failed to move the file pointer to the end of the file!\n");
359 /* Set up thread data */
360 for (size_t i
= 0; i
< 2U; ++i
)
362 threadData
[i
].hOutput
= (i
> 0U) ? hMyFile
: hStdOut
;
363 threadData
[i
].hError
= hStdErr
;
364 threadData
[i
].flush
= flush
&& (!is_terminal(threadData
[i
].hOutput
));
365 threadData
[i
].hEventReady
[0U] = hEventThrdReady
[i
];
366 threadData
[i
].hEventReady
[1U] = hEventStop
;
367 threadData
[i
].hEventCompleted
= hEventCompleted
[i
];
371 for (size_t i
= 0; i
< 2U; ++i
)
373 if (!(hThreads
[i
] = CreateThread(NULL
, 0U, writer_thread_start_routine
, &threadData
[i
], 0U, NULL
)))
375 write_text(hStdErr
, L
"[tee] System error: Failed to create thread!\n");
380 /* Are we reading from a pipe? */
381 const BOOL isPipeInput
= (GetFileType(hStdIn
) == FILE_TYPE_PIPE
);
383 /* Initialize index */
384 ULONG_PTR myIndex
= 1U - index
;
386 /* Process all input from STDIN stream */
389 for (size_t i
= 0U; i
< 2U; ++i
)
391 if (!SetEvent(hEventThrdReady
[i
]))
393 write_text(hStdErr
, L
"[tee] System error: Failed to signal event!\n");
398 if (!ReadFile(hStdIn
, buffer
[myIndex
], BUFFSIZE
, &bytesTotal
[myIndex
], NULL
))
400 if (GetLastError() != ERROR_BROKEN_PIPE
)
402 write_text(hStdErr
, L
"[tee] Error: Failed to read input data!\n");
408 if ((!bytesTotal
[myIndex
]) && (!isPipeInput
)) /*pipes may return zero bytes, even when more data can become available later!*/
413 const DWORD waitResult
= WaitForMultipleObjects(2U, hEventCompleted
, TRUE
, INFINITE
);
414 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
416 write_text(hStdErr
, L
"[tee] System error: Failed to wait for events!\n");
420 myIndex
= (ULONG_PTR
) InterlockedExchangePointer((PVOID
*)&index
, (PVOID
)myIndex
);
422 while ((!g_stop
) || ignore
);
428 /* Stop the worker threads */
431 SetEvent(hEventStop
);
434 /* Wait for worker threads to exit */
437 const DWORD waitResult
= WaitForMultipleObjects(hThreads
[1U] ? 2U : 1U, hThreads
, TRUE
, 12000U);
438 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
440 for (DWORD i
= 0U; i
< (hThreads
[1U] ? 2U : 1U); ++i
)
442 if (WaitForSingleObject(hThreads
[i
], 125U) != WAIT_OBJECT_0
)
444 write_text(hStdErr
, L
"[tee] Error: Worker thread did not exit cleanly!\n");
445 TerminateThread(hThreads
[i
], 1U);
451 /* Close worker threads */
452 for (DWORD i
= 0U; i
< 2U; ++i
)
454 SAFE_CLOSE_HANDLE(hThreads
[i
]);
457 /* Close output file */
458 if (hMyFile
!= INVALID_HANDLE_VALUE
)
462 FlushFileBuffers(hMyFile
);
464 CloseHandle(hMyFile
);
468 for (size_t i
= 0U; i
< 2U; ++i
)
470 SAFE_CLOSE_HANDLE(hEventThrdReady
[i
]);
471 SAFE_CLOSE_HANDLE(hEventCompleted
[i
]);
473 SAFE_CLOSE_HANDLE(hEventStop
);
479 // --------------------------------------------------------------------------
481 // --------------------------------------------------------------------------
483 #pragma warning(disable: 4702)
485 int wmainCRTStartup(void)
487 SetErrorMode(SEM_FAILCRITICALERRORS
);
490 LPWSTR
*const szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
496 const int retval
= wmain(nArgs
, szArglist
);
497 LocalFree(szArglist
);
498 ExitProcess((UINT
)retval
);